sqlglot.generator
1from __future__ import annotations 2 3import logging 4import re 5import typing as t 6from collections import defaultdict 7from functools import reduce, wraps 8 9from sqlglot import exp 10from sqlglot.errors import ErrorLevel, UnsupportedError, concat_messages 11from sqlglot.helper import apply_index_offset, csv, name_sequence, seq_get 12from sqlglot.jsonpath import ALL_JSON_PATH_PARTS, JSON_PATH_PART_TRANSFORMS 13from sqlglot.time import format_time 14from sqlglot.tokens import TokenType 15 16if t.TYPE_CHECKING: 17 from sqlglot._typing import E 18 from sqlglot.dialects.dialect import DialectType 19 20 G = t.TypeVar("G", bound="Generator") 21 GeneratorMethod = t.Callable[[G, E], str] 22 23logger = logging.getLogger("sqlglot") 24 25ESCAPED_UNICODE_RE = re.compile(r"\\(\d+)") 26UNSUPPORTED_TEMPLATE = "Argument '{}' is not supported for expression '{}' when targeting {}." 27 28 29def unsupported_args( 30 *args: t.Union[str, t.Tuple[str, str]], 31) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 32 """ 33 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 34 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 35 """ 36 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 37 for arg in args: 38 if isinstance(arg, str): 39 diagnostic_by_arg[arg] = None 40 else: 41 diagnostic_by_arg[arg[0]] = arg[1] 42 43 def decorator(func: GeneratorMethod) -> GeneratorMethod: 44 @wraps(func) 45 def _func(generator: G, expression: E) -> str: 46 expression_name = expression.__class__.__name__ 47 dialect_name = generator.dialect.__class__.__name__ 48 49 for arg_name, diagnostic in diagnostic_by_arg.items(): 50 if expression.args.get(arg_name): 51 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 52 arg_name, expression_name, dialect_name 53 ) 54 generator.unsupported(diagnostic) 55 56 return func(generator, expression) 57 58 return _func 59 60 return decorator 61 62 63class _Generator(type): 64 def __new__(cls, clsname, bases, attrs): 65 klass = super().__new__(cls, clsname, bases, attrs) 66 67 # Remove transforms that correspond to unsupported JSONPathPart expressions 68 for part in ALL_JSON_PATH_PARTS - klass.SUPPORTED_JSON_PATH_PARTS: 69 klass.TRANSFORMS.pop(part, None) 70 71 return klass 72 73 74class Generator(metaclass=_Generator): 75 """ 76 Generator converts a given syntax tree to the corresponding SQL string. 77 78 Args: 79 pretty: Whether to format the produced SQL string. 80 Default: False. 81 identify: Determines when an identifier should be quoted. Possible values are: 82 False (default): Never quote, except in cases where it's mandatory by the dialect. 83 True or 'always': Always quote. 84 'safe': Only quote identifiers that are case insensitive. 85 normalize: Whether to normalize identifiers to lowercase. 86 Default: False. 87 pad: The pad size in a formatted string. For example, this affects the indentation of 88 a projection in a query, relative to its nesting level. 89 Default: 2. 90 indent: The indentation size in a formatted string. For example, this affects the 91 indentation of subqueries and filters under a `WHERE` clause. 92 Default: 2. 93 normalize_functions: How to normalize function names. Possible values are: 94 "upper" or True (default): Convert names to uppercase. 95 "lower": Convert names to lowercase. 96 False: Disables function name normalization. 97 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 98 Default ErrorLevel.WARN. 99 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 100 This is only relevant if unsupported_level is ErrorLevel.RAISE. 101 Default: 3 102 leading_comma: Whether the comma is leading or trailing in select expressions. 103 This is only relevant when generating in pretty mode. 104 Default: False 105 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 106 The default is on the smaller end because the length only represents a segment and not the true 107 line length. 108 Default: 80 109 comments: Whether to preserve comments in the output SQL code. 110 Default: True 111 """ 112 113 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 114 **JSON_PATH_PART_TRANSFORMS, 115 exp.AllowedValuesProperty: lambda self, 116 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 117 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 118 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 119 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 120 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 121 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 122 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 123 exp.CaseSpecificColumnConstraint: lambda _, 124 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 125 exp.Ceil: lambda self, e: self.ceil_floor(e), 126 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 127 exp.CharacterSetProperty: lambda self, 128 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 129 exp.ClusteredColumnConstraint: lambda self, 130 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 131 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 132 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 133 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 134 exp.ConvertToCharset: lambda self, e: self.func( 135 "CONVERT", e.this, e.args["dest"], e.args.get("source") 136 ), 137 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 138 exp.CredentialsProperty: lambda self, 139 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 140 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 141 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 142 exp.DynamicProperty: lambda *_: "DYNAMIC", 143 exp.EmptyProperty: lambda *_: "EMPTY", 144 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 145 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 146 exp.EphemeralColumnConstraint: lambda self, 147 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 148 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 149 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 150 exp.Except: lambda self, e: self.set_operations(e), 151 exp.ExternalProperty: lambda *_: "EXTERNAL", 152 exp.Floor: lambda self, e: self.ceil_floor(e), 153 exp.Get: lambda self, e: self.get_put_sql(e), 154 exp.GlobalProperty: lambda *_: "GLOBAL", 155 exp.HeapProperty: lambda *_: "HEAP", 156 exp.IcebergProperty: lambda *_: "ICEBERG", 157 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 158 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 159 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 160 exp.Intersect: lambda self, e: self.set_operations(e), 161 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 162 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 163 exp.LanguageProperty: lambda self, e: self.naked_property(e), 164 exp.LocationProperty: lambda self, e: self.naked_property(e), 165 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 166 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 167 exp.NonClusteredColumnConstraint: lambda self, 168 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 169 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 170 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 171 exp.OnCommitProperty: lambda _, 172 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 173 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 174 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 175 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 176 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 177 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 178 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 179 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 180 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 181 exp.ProjectionPolicyColumnConstraint: lambda self, 182 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 183 exp.Put: lambda self, e: self.get_put_sql(e), 184 exp.RemoteWithConnectionModelProperty: lambda self, 185 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 186 exp.ReturnsProperty: lambda self, e: ( 187 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 188 ), 189 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 190 exp.SecureProperty: lambda *_: "SECURE", 191 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 192 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 193 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 194 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 195 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 196 exp.SqlReadWriteProperty: lambda _, e: e.name, 197 exp.SqlSecurityProperty: lambda _, 198 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 199 exp.StabilityProperty: lambda _, e: e.name, 200 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 201 exp.StreamingTableProperty: lambda *_: "STREAMING", 202 exp.StrictProperty: lambda *_: "STRICT", 203 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 204 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 205 exp.TemporaryProperty: lambda *_: "TEMPORARY", 206 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 207 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 208 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 209 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 210 exp.TransientProperty: lambda *_: "TRANSIENT", 211 exp.Union: lambda self, e: self.set_operations(e), 212 exp.UnloggedProperty: lambda *_: "UNLOGGED", 213 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 214 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 215 exp.Uuid: lambda *_: "UUID()", 216 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 217 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 218 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 219 exp.VolatileProperty: lambda *_: "VOLATILE", 220 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 221 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 222 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 223 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 224 exp.ForceProperty: lambda *_: "FORCE", 225 } 226 227 # Whether null ordering is supported in order by 228 # True: Full Support, None: No support, False: No support for certain cases 229 # such as window specifications, aggregate functions etc 230 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 231 232 # Whether ignore nulls is inside the agg or outside. 233 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 234 IGNORE_NULLS_IN_FUNC = False 235 236 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 237 LOCKING_READS_SUPPORTED = False 238 239 # Whether the EXCEPT and INTERSECT operations can return duplicates 240 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 241 242 # Wrap derived values in parens, usually standard but spark doesn't support it 243 WRAP_DERIVED_VALUES = True 244 245 # Whether create function uses an AS before the RETURN 246 CREATE_FUNCTION_RETURN_AS = True 247 248 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 249 MATCHED_BY_SOURCE = True 250 251 # Whether the INTERVAL expression works only with values like '1 day' 252 SINGLE_STRING_INTERVAL = False 253 254 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 255 INTERVAL_ALLOWS_PLURAL_FORM = True 256 257 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 258 LIMIT_FETCH = "ALL" 259 260 # Whether limit and fetch allows expresions or just limits 261 LIMIT_ONLY_LITERALS = False 262 263 # Whether a table is allowed to be renamed with a db 264 RENAME_TABLE_WITH_DB = True 265 266 # The separator for grouping sets and rollups 267 GROUPINGS_SEP = "," 268 269 # The string used for creating an index on a table 270 INDEX_ON = "ON" 271 272 # Whether join hints should be generated 273 JOIN_HINTS = True 274 275 # Whether table hints should be generated 276 TABLE_HINTS = True 277 278 # Whether query hints should be generated 279 QUERY_HINTS = True 280 281 # What kind of separator to use for query hints 282 QUERY_HINT_SEP = ", " 283 284 # Whether comparing against booleans (e.g. x IS TRUE) is supported 285 IS_BOOL_ALLOWED = True 286 287 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 288 DUPLICATE_KEY_UPDATE_WITH_SET = True 289 290 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 291 LIMIT_IS_TOP = False 292 293 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 294 RETURNING_END = True 295 296 # Whether to generate an unquoted value for EXTRACT's date part argument 297 EXTRACT_ALLOWS_QUOTES = True 298 299 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 300 TZ_TO_WITH_TIME_ZONE = False 301 302 # Whether the NVL2 function is supported 303 NVL2_SUPPORTED = True 304 305 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 306 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 307 308 # Whether VALUES statements can be used as derived tables. 309 # MySQL 5 and Redshift do not allow this, so when False, it will convert 310 # SELECT * VALUES into SELECT UNION 311 VALUES_AS_TABLE = True 312 313 # Whether the word COLUMN is included when adding a column with ALTER TABLE 314 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 315 316 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 317 UNNEST_WITH_ORDINALITY = True 318 319 # Whether FILTER (WHERE cond) can be used for conditional aggregation 320 AGGREGATE_FILTER_SUPPORTED = True 321 322 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 323 SEMI_ANTI_JOIN_WITH_SIDE = True 324 325 # Whether to include the type of a computed column in the CREATE DDL 326 COMPUTED_COLUMN_WITH_TYPE = True 327 328 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 329 SUPPORTS_TABLE_COPY = True 330 331 # Whether parentheses are required around the table sample's expression 332 TABLESAMPLE_REQUIRES_PARENS = True 333 334 # Whether a table sample clause's size needs to be followed by the ROWS keyword 335 TABLESAMPLE_SIZE_IS_ROWS = True 336 337 # The keyword(s) to use when generating a sample clause 338 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 339 340 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 341 TABLESAMPLE_WITH_METHOD = True 342 343 # The keyword to use when specifying the seed of a sample clause 344 TABLESAMPLE_SEED_KEYWORD = "SEED" 345 346 # Whether COLLATE is a function instead of a binary operator 347 COLLATE_IS_FUNC = False 348 349 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 350 DATA_TYPE_SPECIFIERS_ALLOWED = False 351 352 # Whether conditions require booleans WHERE x = 0 vs WHERE x 353 ENSURE_BOOLS = False 354 355 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 356 CTE_RECURSIVE_KEYWORD_REQUIRED = True 357 358 # Whether CONCAT requires >1 arguments 359 SUPPORTS_SINGLE_ARG_CONCAT = True 360 361 # Whether LAST_DAY function supports a date part argument 362 LAST_DAY_SUPPORTS_DATE_PART = True 363 364 # Whether named columns are allowed in table aliases 365 SUPPORTS_TABLE_ALIAS_COLUMNS = True 366 367 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 368 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 369 370 # What delimiter to use for separating JSON key/value pairs 371 JSON_KEY_VALUE_PAIR_SEP = ":" 372 373 # INSERT OVERWRITE TABLE x override 374 INSERT_OVERWRITE = " OVERWRITE TABLE" 375 376 # Whether the SELECT .. INTO syntax is used instead of CTAS 377 SUPPORTS_SELECT_INTO = False 378 379 # Whether UNLOGGED tables can be created 380 SUPPORTS_UNLOGGED_TABLES = False 381 382 # Whether the CREATE TABLE LIKE statement is supported 383 SUPPORTS_CREATE_TABLE_LIKE = True 384 385 # Whether the LikeProperty needs to be specified inside of the schema clause 386 LIKE_PROPERTY_INSIDE_SCHEMA = False 387 388 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 389 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 390 MULTI_ARG_DISTINCT = True 391 392 # Whether the JSON extraction operators expect a value of type JSON 393 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 394 395 # Whether bracketed keys like ["foo"] are supported in JSON paths 396 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 397 398 # Whether to escape keys using single quotes in JSON paths 399 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 400 401 # The JSONPathPart expressions supported by this dialect 402 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 403 404 # Whether any(f(x) for x in array) can be implemented by this dialect 405 CAN_IMPLEMENT_ARRAY_ANY = False 406 407 # Whether the function TO_NUMBER is supported 408 SUPPORTS_TO_NUMBER = True 409 410 # Whether EXCLUDE in window specification is supported 411 SUPPORTS_WINDOW_EXCLUDE = False 412 413 # Whether or not set op modifiers apply to the outer set op or select. 414 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 415 # True means limit 1 happens after the set op, False means it it happens on y. 416 SET_OP_MODIFIERS = True 417 418 # Whether parameters from COPY statement are wrapped in parentheses 419 COPY_PARAMS_ARE_WRAPPED = True 420 421 # Whether values of params are set with "=" token or empty space 422 COPY_PARAMS_EQ_REQUIRED = False 423 424 # Whether COPY statement has INTO keyword 425 COPY_HAS_INTO_KEYWORD = True 426 427 # Whether the conditional TRY(expression) function is supported 428 TRY_SUPPORTED = True 429 430 # Whether the UESCAPE syntax in unicode strings is supported 431 SUPPORTS_UESCAPE = True 432 433 # The keyword to use when generating a star projection with excluded columns 434 STAR_EXCEPT = "EXCEPT" 435 436 # The HEX function name 437 HEX_FUNC = "HEX" 438 439 # The keywords to use when prefixing & separating WITH based properties 440 WITH_PROPERTIES_PREFIX = "WITH" 441 442 # Whether to quote the generated expression of exp.JsonPath 443 QUOTE_JSON_PATH = True 444 445 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 446 PAD_FILL_PATTERN_IS_REQUIRED = False 447 448 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 449 SUPPORTS_EXPLODING_PROJECTIONS = True 450 451 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 452 ARRAY_CONCAT_IS_VAR_LEN = True 453 454 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 455 SUPPORTS_CONVERT_TIMEZONE = False 456 457 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 458 SUPPORTS_MEDIAN = True 459 460 # Whether UNIX_SECONDS(timestamp) is supported 461 SUPPORTS_UNIX_SECONDS = False 462 463 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 464 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 465 466 # The function name of the exp.ArraySize expression 467 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 468 469 # The syntax to use when altering the type of a column 470 ALTER_SET_TYPE = "SET DATA TYPE" 471 472 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 473 # None -> Doesn't support it at all 474 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 475 # True (Postgres) -> Explicitly requires it 476 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 477 478 TYPE_MAPPING = { 479 exp.DataType.Type.DATETIME2: "TIMESTAMP", 480 exp.DataType.Type.NCHAR: "CHAR", 481 exp.DataType.Type.NVARCHAR: "VARCHAR", 482 exp.DataType.Type.MEDIUMTEXT: "TEXT", 483 exp.DataType.Type.LONGTEXT: "TEXT", 484 exp.DataType.Type.TINYTEXT: "TEXT", 485 exp.DataType.Type.BLOB: "VARBINARY", 486 exp.DataType.Type.MEDIUMBLOB: "BLOB", 487 exp.DataType.Type.LONGBLOB: "BLOB", 488 exp.DataType.Type.TINYBLOB: "BLOB", 489 exp.DataType.Type.INET: "INET", 490 exp.DataType.Type.ROWVERSION: "VARBINARY", 491 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 492 } 493 494 TIME_PART_SINGULARS = { 495 "MICROSECONDS": "MICROSECOND", 496 "SECONDS": "SECOND", 497 "MINUTES": "MINUTE", 498 "HOURS": "HOUR", 499 "DAYS": "DAY", 500 "WEEKS": "WEEK", 501 "MONTHS": "MONTH", 502 "QUARTERS": "QUARTER", 503 "YEARS": "YEAR", 504 } 505 506 AFTER_HAVING_MODIFIER_TRANSFORMS = { 507 "cluster": lambda self, e: self.sql(e, "cluster"), 508 "distribute": lambda self, e: self.sql(e, "distribute"), 509 "sort": lambda self, e: self.sql(e, "sort"), 510 "windows": lambda self, e: ( 511 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 512 if e.args.get("windows") 513 else "" 514 ), 515 "qualify": lambda self, e: self.sql(e, "qualify"), 516 } 517 518 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 519 520 STRUCT_DELIMITER = ("<", ">") 521 522 PARAMETER_TOKEN = "@" 523 NAMED_PLACEHOLDER_TOKEN = ":" 524 525 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 526 527 PROPERTIES_LOCATION = { 528 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 529 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 530 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 531 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 532 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 533 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 534 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 535 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 536 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 537 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 538 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 539 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 540 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 541 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 542 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 543 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 544 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 545 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 546 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 547 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 548 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 549 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 552 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 553 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 556 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 557 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 558 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 559 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 560 exp.HeapProperty: exp.Properties.Location.POST_WITH, 561 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 562 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 563 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 564 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 566 exp.JournalProperty: exp.Properties.Location.POST_NAME, 567 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 568 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 572 exp.LogProperty: exp.Properties.Location.POST_NAME, 573 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 574 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 575 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 576 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 577 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 578 exp.Order: exp.Properties.Location.POST_SCHEMA, 579 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 580 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 581 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 582 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 583 exp.Property: exp.Properties.Location.POST_WITH, 584 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 585 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 592 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 593 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 594 exp.Set: exp.Properties.Location.POST_SCHEMA, 595 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 596 exp.SetProperty: exp.Properties.Location.POST_CREATE, 597 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 598 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 599 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 600 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 601 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 603 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 604 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 606 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 607 exp.Tags: exp.Properties.Location.POST_WITH, 608 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 609 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 610 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 611 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 612 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 613 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 614 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 615 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 617 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 618 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 619 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 620 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 621 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 623 } 624 625 # Keywords that can't be used as unquoted identifier names 626 RESERVED_KEYWORDS: t.Set[str] = set() 627 628 # Expressions whose comments are separated from them for better formatting 629 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 630 exp.Command, 631 exp.Create, 632 exp.Describe, 633 exp.Delete, 634 exp.Drop, 635 exp.From, 636 exp.Insert, 637 exp.Join, 638 exp.MultitableInserts, 639 exp.Select, 640 exp.SetOperation, 641 exp.Update, 642 exp.Where, 643 exp.With, 644 ) 645 646 # Expressions that should not have their comments generated in maybe_comment 647 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 648 exp.Binary, 649 exp.SetOperation, 650 ) 651 652 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 653 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 654 exp.Column, 655 exp.Literal, 656 exp.Neg, 657 exp.Paren, 658 ) 659 660 PARAMETERIZABLE_TEXT_TYPES = { 661 exp.DataType.Type.NVARCHAR, 662 exp.DataType.Type.VARCHAR, 663 exp.DataType.Type.CHAR, 664 exp.DataType.Type.NCHAR, 665 } 666 667 # Expressions that need to have all CTEs under them bubbled up to them 668 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 669 670 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 671 672 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 673 674 __slots__ = ( 675 "pretty", 676 "identify", 677 "normalize", 678 "pad", 679 "_indent", 680 "normalize_functions", 681 "unsupported_level", 682 "max_unsupported", 683 "leading_comma", 684 "max_text_width", 685 "comments", 686 "dialect", 687 "unsupported_messages", 688 "_escaped_quote_end", 689 "_escaped_identifier_end", 690 "_next_name", 691 "_identifier_start", 692 "_identifier_end", 693 "_quote_json_path_key_using_brackets", 694 ) 695 696 def __init__( 697 self, 698 pretty: t.Optional[bool] = None, 699 identify: str | bool = False, 700 normalize: bool = False, 701 pad: int = 2, 702 indent: int = 2, 703 normalize_functions: t.Optional[str | bool] = None, 704 unsupported_level: ErrorLevel = ErrorLevel.WARN, 705 max_unsupported: int = 3, 706 leading_comma: bool = False, 707 max_text_width: int = 80, 708 comments: bool = True, 709 dialect: DialectType = None, 710 ): 711 import sqlglot 712 from sqlglot.dialects import Dialect 713 714 self.pretty = pretty if pretty is not None else sqlglot.pretty 715 self.identify = identify 716 self.normalize = normalize 717 self.pad = pad 718 self._indent = indent 719 self.unsupported_level = unsupported_level 720 self.max_unsupported = max_unsupported 721 self.leading_comma = leading_comma 722 self.max_text_width = max_text_width 723 self.comments = comments 724 self.dialect = Dialect.get_or_raise(dialect) 725 726 # This is both a Dialect property and a Generator argument, so we prioritize the latter 727 self.normalize_functions = ( 728 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 729 ) 730 731 self.unsupported_messages: t.List[str] = [] 732 self._escaped_quote_end: str = ( 733 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 734 ) 735 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 736 737 self._next_name = name_sequence("_t") 738 739 self._identifier_start = self.dialect.IDENTIFIER_START 740 self._identifier_end = self.dialect.IDENTIFIER_END 741 742 self._quote_json_path_key_using_brackets = True 743 744 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 745 """ 746 Generates the SQL string corresponding to the given syntax tree. 747 748 Args: 749 expression: The syntax tree. 750 copy: Whether to copy the expression. The generator performs mutations so 751 it is safer to copy. 752 753 Returns: 754 The SQL string corresponding to `expression`. 755 """ 756 if copy: 757 expression = expression.copy() 758 759 expression = self.preprocess(expression) 760 761 self.unsupported_messages = [] 762 sql = self.sql(expression).strip() 763 764 if self.pretty: 765 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 766 767 if self.unsupported_level == ErrorLevel.IGNORE: 768 return sql 769 770 if self.unsupported_level == ErrorLevel.WARN: 771 for msg in self.unsupported_messages: 772 logger.warning(msg) 773 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 774 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 775 776 return sql 777 778 def preprocess(self, expression: exp.Expression) -> exp.Expression: 779 """Apply generic preprocessing transformations to a given expression.""" 780 expression = self._move_ctes_to_top_level(expression) 781 782 if self.ENSURE_BOOLS: 783 from sqlglot.transforms import ensure_bools 784 785 expression = ensure_bools(expression) 786 787 return expression 788 789 def _move_ctes_to_top_level(self, expression: E) -> E: 790 if ( 791 not expression.parent 792 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 793 and any(node.parent is not expression for node in expression.find_all(exp.With)) 794 ): 795 from sqlglot.transforms import move_ctes_to_top_level 796 797 expression = move_ctes_to_top_level(expression) 798 return expression 799 800 def unsupported(self, message: str) -> None: 801 if self.unsupported_level == ErrorLevel.IMMEDIATE: 802 raise UnsupportedError(message) 803 self.unsupported_messages.append(message) 804 805 def sep(self, sep: str = " ") -> str: 806 return f"{sep.strip()}\n" if self.pretty else sep 807 808 def seg(self, sql: str, sep: str = " ") -> str: 809 return f"{self.sep(sep)}{sql}" 810 811 def pad_comment(self, comment: str) -> str: 812 comment = " " + comment if comment[0].strip() else comment 813 comment = comment + " " if comment[-1].strip() else comment 814 return comment 815 816 def maybe_comment( 817 self, 818 sql: str, 819 expression: t.Optional[exp.Expression] = None, 820 comments: t.Optional[t.List[str]] = None, 821 separated: bool = False, 822 ) -> str: 823 comments = ( 824 ((expression and expression.comments) if comments is None else comments) # type: ignore 825 if self.comments 826 else None 827 ) 828 829 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 830 return sql 831 832 comments_sql = " ".join( 833 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 834 ) 835 836 if not comments_sql: 837 return sql 838 839 comments_sql = self._replace_line_breaks(comments_sql) 840 841 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 842 return ( 843 f"{self.sep()}{comments_sql}{sql}" 844 if not sql or sql[0].isspace() 845 else f"{comments_sql}{self.sep()}{sql}" 846 ) 847 848 return f"{sql} {comments_sql}" 849 850 def wrap(self, expression: exp.Expression | str) -> str: 851 this_sql = ( 852 self.sql(expression) 853 if isinstance(expression, exp.UNWRAPPED_QUERIES) 854 else self.sql(expression, "this") 855 ) 856 if not this_sql: 857 return "()" 858 859 this_sql = self.indent(this_sql, level=1, pad=0) 860 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 861 862 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 863 original = self.identify 864 self.identify = False 865 result = func(*args, **kwargs) 866 self.identify = original 867 return result 868 869 def normalize_func(self, name: str) -> str: 870 if self.normalize_functions == "upper" or self.normalize_functions is True: 871 return name.upper() 872 if self.normalize_functions == "lower": 873 return name.lower() 874 return name 875 876 def indent( 877 self, 878 sql: str, 879 level: int = 0, 880 pad: t.Optional[int] = None, 881 skip_first: bool = False, 882 skip_last: bool = False, 883 ) -> str: 884 if not self.pretty or not sql: 885 return sql 886 887 pad = self.pad if pad is None else pad 888 lines = sql.split("\n") 889 890 return "\n".join( 891 ( 892 line 893 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 894 else f"{' ' * (level * self._indent + pad)}{line}" 895 ) 896 for i, line in enumerate(lines) 897 ) 898 899 def sql( 900 self, 901 expression: t.Optional[str | exp.Expression], 902 key: t.Optional[str] = None, 903 comment: bool = True, 904 ) -> str: 905 if not expression: 906 return "" 907 908 if isinstance(expression, str): 909 return expression 910 911 if key: 912 value = expression.args.get(key) 913 if value: 914 return self.sql(value) 915 return "" 916 917 transform = self.TRANSFORMS.get(expression.__class__) 918 919 if callable(transform): 920 sql = transform(self, expression) 921 elif isinstance(expression, exp.Expression): 922 exp_handler_name = f"{expression.key}_sql" 923 924 if hasattr(self, exp_handler_name): 925 sql = getattr(self, exp_handler_name)(expression) 926 elif isinstance(expression, exp.Func): 927 sql = self.function_fallback_sql(expression) 928 elif isinstance(expression, exp.Property): 929 sql = self.property_sql(expression) 930 else: 931 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 932 else: 933 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 934 935 return self.maybe_comment(sql, expression) if self.comments and comment else sql 936 937 def uncache_sql(self, expression: exp.Uncache) -> str: 938 table = self.sql(expression, "this") 939 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 940 return f"UNCACHE TABLE{exists_sql} {table}" 941 942 def cache_sql(self, expression: exp.Cache) -> str: 943 lazy = " LAZY" if expression.args.get("lazy") else "" 944 table = self.sql(expression, "this") 945 options = expression.args.get("options") 946 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 947 sql = self.sql(expression, "expression") 948 sql = f" AS{self.sep()}{sql}" if sql else "" 949 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 950 return self.prepend_ctes(expression, sql) 951 952 def characterset_sql(self, expression: exp.CharacterSet) -> str: 953 if isinstance(expression.parent, exp.Cast): 954 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 955 default = "DEFAULT " if expression.args.get("default") else "" 956 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 957 958 def column_parts(self, expression: exp.Column) -> str: 959 return ".".join( 960 self.sql(part) 961 for part in ( 962 expression.args.get("catalog"), 963 expression.args.get("db"), 964 expression.args.get("table"), 965 expression.args.get("this"), 966 ) 967 if part 968 ) 969 970 def column_sql(self, expression: exp.Column) -> str: 971 join_mark = " (+)" if expression.args.get("join_mark") else "" 972 973 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 974 join_mark = "" 975 self.unsupported("Outer join syntax using the (+) operator is not supported.") 976 977 return f"{self.column_parts(expression)}{join_mark}" 978 979 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 980 this = self.sql(expression, "this") 981 this = f" {this}" if this else "" 982 position = self.sql(expression, "position") 983 return f"{position}{this}" 984 985 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 986 column = self.sql(expression, "this") 987 kind = self.sql(expression, "kind") 988 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 989 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 990 kind = f"{sep}{kind}" if kind else "" 991 constraints = f" {constraints}" if constraints else "" 992 position = self.sql(expression, "position") 993 position = f" {position}" if position else "" 994 995 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 996 kind = "" 997 998 return f"{exists}{column}{kind}{constraints}{position}" 999 1000 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1001 this = self.sql(expression, "this") 1002 kind_sql = self.sql(expression, "kind").strip() 1003 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1004 1005 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1006 this = self.sql(expression, "this") 1007 if expression.args.get("not_null"): 1008 persisted = " PERSISTED NOT NULL" 1009 elif expression.args.get("persisted"): 1010 persisted = " PERSISTED" 1011 else: 1012 persisted = "" 1013 return f"AS {this}{persisted}" 1014 1015 def autoincrementcolumnconstraint_sql(self, _) -> str: 1016 return self.token_sql(TokenType.AUTO_INCREMENT) 1017 1018 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1019 if isinstance(expression.this, list): 1020 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1021 else: 1022 this = self.sql(expression, "this") 1023 1024 return f"COMPRESS {this}" 1025 1026 def generatedasidentitycolumnconstraint_sql( 1027 self, expression: exp.GeneratedAsIdentityColumnConstraint 1028 ) -> str: 1029 this = "" 1030 if expression.this is not None: 1031 on_null = " ON NULL" if expression.args.get("on_null") else "" 1032 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1033 1034 start = expression.args.get("start") 1035 start = f"START WITH {start}" if start else "" 1036 increment = expression.args.get("increment") 1037 increment = f" INCREMENT BY {increment}" if increment else "" 1038 minvalue = expression.args.get("minvalue") 1039 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1040 maxvalue = expression.args.get("maxvalue") 1041 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1042 cycle = expression.args.get("cycle") 1043 cycle_sql = "" 1044 1045 if cycle is not None: 1046 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1047 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1048 1049 sequence_opts = "" 1050 if start or increment or cycle_sql: 1051 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1052 sequence_opts = f" ({sequence_opts.strip()})" 1053 1054 expr = self.sql(expression, "expression") 1055 expr = f"({expr})" if expr else "IDENTITY" 1056 1057 return f"GENERATED{this} AS {expr}{sequence_opts}" 1058 1059 def generatedasrowcolumnconstraint_sql( 1060 self, expression: exp.GeneratedAsRowColumnConstraint 1061 ) -> str: 1062 start = "START" if expression.args.get("start") else "END" 1063 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1064 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1065 1066 def periodforsystemtimeconstraint_sql( 1067 self, expression: exp.PeriodForSystemTimeConstraint 1068 ) -> str: 1069 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1070 1071 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1072 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1073 1074 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 1075 return f"AS {self.sql(expression, 'this')}" 1076 1077 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1078 desc = expression.args.get("desc") 1079 if desc is not None: 1080 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1081 options = self.expressions(expression, key="options", flat=True, sep=" ") 1082 options = f" {options}" if options else "" 1083 return f"PRIMARY KEY{options}" 1084 1085 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1086 this = self.sql(expression, "this") 1087 this = f" {this}" if this else "" 1088 index_type = expression.args.get("index_type") 1089 index_type = f" USING {index_type}" if index_type else "" 1090 on_conflict = self.sql(expression, "on_conflict") 1091 on_conflict = f" {on_conflict}" if on_conflict else "" 1092 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1093 options = self.expressions(expression, key="options", flat=True, sep=" ") 1094 options = f" {options}" if options else "" 1095 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1096 1097 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1098 return self.sql(expression, "this") 1099 1100 def create_sql(self, expression: exp.Create) -> str: 1101 kind = self.sql(expression, "kind") 1102 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1103 properties = expression.args.get("properties") 1104 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1105 1106 this = self.createable_sql(expression, properties_locs) 1107 1108 properties_sql = "" 1109 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1110 exp.Properties.Location.POST_WITH 1111 ): 1112 properties_sql = self.sql( 1113 exp.Properties( 1114 expressions=[ 1115 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1116 *properties_locs[exp.Properties.Location.POST_WITH], 1117 ] 1118 ) 1119 ) 1120 1121 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1122 properties_sql = self.sep() + properties_sql 1123 elif not self.pretty: 1124 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1125 properties_sql = f" {properties_sql}" 1126 1127 begin = " BEGIN" if expression.args.get("begin") else "" 1128 end = " END" if expression.args.get("end") else "" 1129 1130 expression_sql = self.sql(expression, "expression") 1131 if expression_sql: 1132 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1133 1134 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1135 postalias_props_sql = "" 1136 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1137 postalias_props_sql = self.properties( 1138 exp.Properties( 1139 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1140 ), 1141 wrapped=False, 1142 ) 1143 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1144 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1145 1146 postindex_props_sql = "" 1147 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1148 postindex_props_sql = self.properties( 1149 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1150 wrapped=False, 1151 prefix=" ", 1152 ) 1153 1154 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1155 indexes = f" {indexes}" if indexes else "" 1156 index_sql = indexes + postindex_props_sql 1157 1158 replace = " OR REPLACE" if expression.args.get("replace") else "" 1159 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1160 unique = " UNIQUE" if expression.args.get("unique") else "" 1161 1162 clustered = expression.args.get("clustered") 1163 if clustered is None: 1164 clustered_sql = "" 1165 elif clustered: 1166 clustered_sql = " CLUSTERED COLUMNSTORE" 1167 else: 1168 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1169 1170 postcreate_props_sql = "" 1171 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1172 postcreate_props_sql = self.properties( 1173 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1174 sep=" ", 1175 prefix=" ", 1176 wrapped=False, 1177 ) 1178 1179 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1180 1181 postexpression_props_sql = "" 1182 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1183 postexpression_props_sql = self.properties( 1184 exp.Properties( 1185 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1186 ), 1187 sep=" ", 1188 prefix=" ", 1189 wrapped=False, 1190 ) 1191 1192 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1193 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1194 no_schema_binding = ( 1195 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1196 ) 1197 1198 clone = self.sql(expression, "clone") 1199 clone = f" {clone}" if clone else "" 1200 1201 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1202 properties_expression = f"{expression_sql}{properties_sql}" 1203 else: 1204 properties_expression = f"{properties_sql}{expression_sql}" 1205 1206 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1207 return self.prepend_ctes(expression, expression_sql) 1208 1209 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1210 start = self.sql(expression, "start") 1211 start = f"START WITH {start}" if start else "" 1212 increment = self.sql(expression, "increment") 1213 increment = f" INCREMENT BY {increment}" if increment else "" 1214 minvalue = self.sql(expression, "minvalue") 1215 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1216 maxvalue = self.sql(expression, "maxvalue") 1217 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1218 owned = self.sql(expression, "owned") 1219 owned = f" OWNED BY {owned}" if owned else "" 1220 1221 cache = expression.args.get("cache") 1222 if cache is None: 1223 cache_str = "" 1224 elif cache is True: 1225 cache_str = " CACHE" 1226 else: 1227 cache_str = f" CACHE {cache}" 1228 1229 options = self.expressions(expression, key="options", flat=True, sep=" ") 1230 options = f" {options}" if options else "" 1231 1232 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1233 1234 def clone_sql(self, expression: exp.Clone) -> str: 1235 this = self.sql(expression, "this") 1236 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1237 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1238 return f"{shallow}{keyword} {this}" 1239 1240 def describe_sql(self, expression: exp.Describe) -> str: 1241 style = expression.args.get("style") 1242 style = f" {style}" if style else "" 1243 partition = self.sql(expression, "partition") 1244 partition = f" {partition}" if partition else "" 1245 format = self.sql(expression, "format") 1246 format = f" {format}" if format else "" 1247 1248 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1249 1250 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1251 tag = self.sql(expression, "tag") 1252 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1253 1254 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1255 with_ = self.sql(expression, "with") 1256 if with_: 1257 sql = f"{with_}{self.sep()}{sql}" 1258 return sql 1259 1260 def with_sql(self, expression: exp.With) -> str: 1261 sql = self.expressions(expression, flat=True) 1262 recursive = ( 1263 "RECURSIVE " 1264 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1265 else "" 1266 ) 1267 search = self.sql(expression, "search") 1268 search = f" {search}" if search else "" 1269 1270 return f"WITH {recursive}{sql}{search}" 1271 1272 def cte_sql(self, expression: exp.CTE) -> str: 1273 alias = expression.args.get("alias") 1274 if alias: 1275 alias.add_comments(expression.pop_comments()) 1276 1277 alias_sql = self.sql(expression, "alias") 1278 1279 materialized = expression.args.get("materialized") 1280 if materialized is False: 1281 materialized = "NOT MATERIALIZED " 1282 elif materialized: 1283 materialized = "MATERIALIZED " 1284 1285 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1286 1287 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1288 alias = self.sql(expression, "this") 1289 columns = self.expressions(expression, key="columns", flat=True) 1290 columns = f"({columns})" if columns else "" 1291 1292 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1293 columns = "" 1294 self.unsupported("Named columns are not supported in table alias.") 1295 1296 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1297 alias = self._next_name() 1298 1299 return f"{alias}{columns}" 1300 1301 def bitstring_sql(self, expression: exp.BitString) -> str: 1302 this = self.sql(expression, "this") 1303 if self.dialect.BIT_START: 1304 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1305 return f"{int(this, 2)}" 1306 1307 def hexstring_sql( 1308 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1309 ) -> str: 1310 this = self.sql(expression, "this") 1311 is_integer_type = expression.args.get("is_integer") 1312 1313 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1314 not self.dialect.HEX_START and not binary_function_repr 1315 ): 1316 # Integer representation will be returned if: 1317 # - The read dialect treats the hex value as integer literal but not the write 1318 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1319 return f"{int(this, 16)}" 1320 1321 if not is_integer_type: 1322 # Read dialect treats the hex value as BINARY/BLOB 1323 if binary_function_repr: 1324 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1325 return self.func(binary_function_repr, exp.Literal.string(this)) 1326 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1327 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1328 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1329 1330 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1331 1332 def bytestring_sql(self, expression: exp.ByteString) -> str: 1333 this = self.sql(expression, "this") 1334 if self.dialect.BYTE_START: 1335 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1336 return this 1337 1338 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1339 this = self.sql(expression, "this") 1340 escape = expression.args.get("escape") 1341 1342 if self.dialect.UNICODE_START: 1343 escape_substitute = r"\\\1" 1344 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1345 else: 1346 escape_substitute = r"\\u\1" 1347 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1348 1349 if escape: 1350 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1351 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1352 else: 1353 escape_pattern = ESCAPED_UNICODE_RE 1354 escape_sql = "" 1355 1356 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1357 this = escape_pattern.sub(escape_substitute, this) 1358 1359 return f"{left_quote}{this}{right_quote}{escape_sql}" 1360 1361 def rawstring_sql(self, expression: exp.RawString) -> str: 1362 string = expression.this 1363 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1364 string = string.replace("\\", "\\\\") 1365 1366 string = self.escape_str(string, escape_backslash=False) 1367 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1368 1369 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1370 this = self.sql(expression, "this") 1371 specifier = self.sql(expression, "expression") 1372 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1373 return f"{this}{specifier}" 1374 1375 def datatype_sql(self, expression: exp.DataType) -> str: 1376 nested = "" 1377 values = "" 1378 interior = self.expressions(expression, flat=True) 1379 1380 type_value = expression.this 1381 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1382 type_sql = self.sql(expression, "kind") 1383 else: 1384 type_sql = ( 1385 self.TYPE_MAPPING.get(type_value, type_value.value) 1386 if isinstance(type_value, exp.DataType.Type) 1387 else type_value 1388 ) 1389 1390 if interior: 1391 if expression.args.get("nested"): 1392 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1393 if expression.args.get("values") is not None: 1394 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1395 values = self.expressions(expression, key="values", flat=True) 1396 values = f"{delimiters[0]}{values}{delimiters[1]}" 1397 elif type_value == exp.DataType.Type.INTERVAL: 1398 nested = f" {interior}" 1399 else: 1400 nested = f"({interior})" 1401 1402 type_sql = f"{type_sql}{nested}{values}" 1403 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1404 exp.DataType.Type.TIMETZ, 1405 exp.DataType.Type.TIMESTAMPTZ, 1406 ): 1407 type_sql = f"{type_sql} WITH TIME ZONE" 1408 1409 return type_sql 1410 1411 def directory_sql(self, expression: exp.Directory) -> str: 1412 local = "LOCAL " if expression.args.get("local") else "" 1413 row_format = self.sql(expression, "row_format") 1414 row_format = f" {row_format}" if row_format else "" 1415 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1416 1417 def delete_sql(self, expression: exp.Delete) -> str: 1418 this = self.sql(expression, "this") 1419 this = f" FROM {this}" if this else "" 1420 using = self.sql(expression, "using") 1421 using = f" USING {using}" if using else "" 1422 cluster = self.sql(expression, "cluster") 1423 cluster = f" {cluster}" if cluster else "" 1424 where = self.sql(expression, "where") 1425 returning = self.sql(expression, "returning") 1426 limit = self.sql(expression, "limit") 1427 tables = self.expressions(expression, key="tables") 1428 tables = f" {tables}" if tables else "" 1429 if self.RETURNING_END: 1430 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1431 else: 1432 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1433 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1434 1435 def drop_sql(self, expression: exp.Drop) -> str: 1436 this = self.sql(expression, "this") 1437 expressions = self.expressions(expression, flat=True) 1438 expressions = f" ({expressions})" if expressions else "" 1439 kind = expression.args["kind"] 1440 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1441 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1442 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1443 on_cluster = self.sql(expression, "cluster") 1444 on_cluster = f" {on_cluster}" if on_cluster else "" 1445 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1446 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1447 cascade = " CASCADE" if expression.args.get("cascade") else "" 1448 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1449 purge = " PURGE" if expression.args.get("purge") else "" 1450 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1451 1452 def set_operation(self, expression: exp.SetOperation) -> str: 1453 op_type = type(expression) 1454 op_name = op_type.key.upper() 1455 1456 distinct = expression.args.get("distinct") 1457 if ( 1458 distinct is False 1459 and op_type in (exp.Except, exp.Intersect) 1460 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1461 ): 1462 self.unsupported(f"{op_name} ALL is not supported") 1463 1464 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1465 1466 if distinct is None: 1467 distinct = default_distinct 1468 if distinct is None: 1469 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1470 1471 if distinct is default_distinct: 1472 distinct_or_all = "" 1473 else: 1474 distinct_or_all = " DISTINCT" if distinct else " ALL" 1475 1476 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1477 side_kind = f"{side_kind} " if side_kind else "" 1478 1479 by_name = " BY NAME" if expression.args.get("by_name") else "" 1480 on = self.expressions(expression, key="on", flat=True) 1481 on = f" ON ({on})" if on else "" 1482 1483 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1484 1485 def set_operations(self, expression: exp.SetOperation) -> str: 1486 if not self.SET_OP_MODIFIERS: 1487 limit = expression.args.get("limit") 1488 order = expression.args.get("order") 1489 1490 if limit or order: 1491 select = self._move_ctes_to_top_level( 1492 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1493 ) 1494 1495 if limit: 1496 select = select.limit(limit.pop(), copy=False) 1497 if order: 1498 select = select.order_by(order.pop(), copy=False) 1499 return self.sql(select) 1500 1501 sqls: t.List[str] = [] 1502 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1503 1504 while stack: 1505 node = stack.pop() 1506 1507 if isinstance(node, exp.SetOperation): 1508 stack.append(node.expression) 1509 stack.append( 1510 self.maybe_comment( 1511 self.set_operation(node), comments=node.comments, separated=True 1512 ) 1513 ) 1514 stack.append(node.this) 1515 else: 1516 sqls.append(self.sql(node)) 1517 1518 this = self.sep().join(sqls) 1519 this = self.query_modifiers(expression, this) 1520 return self.prepend_ctes(expression, this) 1521 1522 def fetch_sql(self, expression: exp.Fetch) -> str: 1523 direction = expression.args.get("direction") 1524 direction = f" {direction}" if direction else "" 1525 count = self.sql(expression, "count") 1526 count = f" {count}" if count else "" 1527 limit_options = self.sql(expression, "limit_options") 1528 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1529 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1530 1531 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1532 percent = " PERCENT" if expression.args.get("percent") else "" 1533 rows = " ROWS" if expression.args.get("rows") else "" 1534 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1535 if not with_ties and rows: 1536 with_ties = " ONLY" 1537 return f"{percent}{rows}{with_ties}" 1538 1539 def filter_sql(self, expression: exp.Filter) -> str: 1540 if self.AGGREGATE_FILTER_SUPPORTED: 1541 this = self.sql(expression, "this") 1542 where = self.sql(expression, "expression").strip() 1543 return f"{this} FILTER({where})" 1544 1545 agg = expression.this 1546 agg_arg = agg.this 1547 cond = expression.expression.this 1548 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1549 return self.sql(agg) 1550 1551 def hint_sql(self, expression: exp.Hint) -> str: 1552 if not self.QUERY_HINTS: 1553 self.unsupported("Hints are not supported") 1554 return "" 1555 1556 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1557 1558 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1559 using = self.sql(expression, "using") 1560 using = f" USING {using}" if using else "" 1561 columns = self.expressions(expression, key="columns", flat=True) 1562 columns = f"({columns})" if columns else "" 1563 partition_by = self.expressions(expression, key="partition_by", flat=True) 1564 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1565 where = self.sql(expression, "where") 1566 include = self.expressions(expression, key="include", flat=True) 1567 if include: 1568 include = f" INCLUDE ({include})" 1569 with_storage = self.expressions(expression, key="with_storage", flat=True) 1570 with_storage = f" WITH ({with_storage})" if with_storage else "" 1571 tablespace = self.sql(expression, "tablespace") 1572 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1573 on = self.sql(expression, "on") 1574 on = f" ON {on}" if on else "" 1575 1576 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1577 1578 def index_sql(self, expression: exp.Index) -> str: 1579 unique = "UNIQUE " if expression.args.get("unique") else "" 1580 primary = "PRIMARY " if expression.args.get("primary") else "" 1581 amp = "AMP " if expression.args.get("amp") else "" 1582 name = self.sql(expression, "this") 1583 name = f"{name} " if name else "" 1584 table = self.sql(expression, "table") 1585 table = f"{self.INDEX_ON} {table}" if table else "" 1586 1587 index = "INDEX " if not table else "" 1588 1589 params = self.sql(expression, "params") 1590 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1591 1592 def identifier_sql(self, expression: exp.Identifier) -> str: 1593 text = expression.name 1594 lower = text.lower() 1595 text = lower if self.normalize and not expression.quoted else text 1596 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1597 if ( 1598 expression.quoted 1599 or self.dialect.can_identify(text, self.identify) 1600 or lower in self.RESERVED_KEYWORDS 1601 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1602 ): 1603 text = f"{self._identifier_start}{text}{self._identifier_end}" 1604 return text 1605 1606 def hex_sql(self, expression: exp.Hex) -> str: 1607 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1608 if self.dialect.HEX_LOWERCASE: 1609 text = self.func("LOWER", text) 1610 1611 return text 1612 1613 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1614 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1615 if not self.dialect.HEX_LOWERCASE: 1616 text = self.func("LOWER", text) 1617 return text 1618 1619 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1620 input_format = self.sql(expression, "input_format") 1621 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1622 output_format = self.sql(expression, "output_format") 1623 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1624 return self.sep().join((input_format, output_format)) 1625 1626 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1627 string = self.sql(exp.Literal.string(expression.name)) 1628 return f"{prefix}{string}" 1629 1630 def partition_sql(self, expression: exp.Partition) -> str: 1631 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1632 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1633 1634 def properties_sql(self, expression: exp.Properties) -> str: 1635 root_properties = [] 1636 with_properties = [] 1637 1638 for p in expression.expressions: 1639 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1640 if p_loc == exp.Properties.Location.POST_WITH: 1641 with_properties.append(p) 1642 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1643 root_properties.append(p) 1644 1645 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1646 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1647 1648 if root_props and with_props and not self.pretty: 1649 with_props = " " + with_props 1650 1651 return root_props + with_props 1652 1653 def root_properties(self, properties: exp.Properties) -> str: 1654 if properties.expressions: 1655 return self.expressions(properties, indent=False, sep=" ") 1656 return "" 1657 1658 def properties( 1659 self, 1660 properties: exp.Properties, 1661 prefix: str = "", 1662 sep: str = ", ", 1663 suffix: str = "", 1664 wrapped: bool = True, 1665 ) -> str: 1666 if properties.expressions: 1667 expressions = self.expressions(properties, sep=sep, indent=False) 1668 if expressions: 1669 expressions = self.wrap(expressions) if wrapped else expressions 1670 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1671 return "" 1672 1673 def with_properties(self, properties: exp.Properties) -> str: 1674 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1675 1676 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1677 properties_locs = defaultdict(list) 1678 for p in properties.expressions: 1679 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1680 if p_loc != exp.Properties.Location.UNSUPPORTED: 1681 properties_locs[p_loc].append(p) 1682 else: 1683 self.unsupported(f"Unsupported property {p.key}") 1684 1685 return properties_locs 1686 1687 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1688 if isinstance(expression.this, exp.Dot): 1689 return self.sql(expression, "this") 1690 return f"'{expression.name}'" if string_key else expression.name 1691 1692 def property_sql(self, expression: exp.Property) -> str: 1693 property_cls = expression.__class__ 1694 if property_cls == exp.Property: 1695 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1696 1697 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1698 if not property_name: 1699 self.unsupported(f"Unsupported property {expression.key}") 1700 1701 return f"{property_name}={self.sql(expression, 'this')}" 1702 1703 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1704 if self.SUPPORTS_CREATE_TABLE_LIKE: 1705 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1706 options = f" {options}" if options else "" 1707 1708 like = f"LIKE {self.sql(expression, 'this')}{options}" 1709 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1710 like = f"({like})" 1711 1712 return like 1713 1714 if expression.expressions: 1715 self.unsupported("Transpilation of LIKE property options is unsupported") 1716 1717 select = exp.select("*").from_(expression.this).limit(0) 1718 return f"AS {self.sql(select)}" 1719 1720 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1721 no = "NO " if expression.args.get("no") else "" 1722 protection = " PROTECTION" if expression.args.get("protection") else "" 1723 return f"{no}FALLBACK{protection}" 1724 1725 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1726 no = "NO " if expression.args.get("no") else "" 1727 local = expression.args.get("local") 1728 local = f"{local} " if local else "" 1729 dual = "DUAL " if expression.args.get("dual") else "" 1730 before = "BEFORE " if expression.args.get("before") else "" 1731 after = "AFTER " if expression.args.get("after") else "" 1732 return f"{no}{local}{dual}{before}{after}JOURNAL" 1733 1734 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1735 freespace = self.sql(expression, "this") 1736 percent = " PERCENT" if expression.args.get("percent") else "" 1737 return f"FREESPACE={freespace}{percent}" 1738 1739 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1740 if expression.args.get("default"): 1741 property = "DEFAULT" 1742 elif expression.args.get("on"): 1743 property = "ON" 1744 else: 1745 property = "OFF" 1746 return f"CHECKSUM={property}" 1747 1748 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1749 if expression.args.get("no"): 1750 return "NO MERGEBLOCKRATIO" 1751 if expression.args.get("default"): 1752 return "DEFAULT MERGEBLOCKRATIO" 1753 1754 percent = " PERCENT" if expression.args.get("percent") else "" 1755 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1756 1757 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1758 default = expression.args.get("default") 1759 minimum = expression.args.get("minimum") 1760 maximum = expression.args.get("maximum") 1761 if default or minimum or maximum: 1762 if default: 1763 prop = "DEFAULT" 1764 elif minimum: 1765 prop = "MINIMUM" 1766 else: 1767 prop = "MAXIMUM" 1768 return f"{prop} DATABLOCKSIZE" 1769 units = expression.args.get("units") 1770 units = f" {units}" if units else "" 1771 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1772 1773 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1774 autotemp = expression.args.get("autotemp") 1775 always = expression.args.get("always") 1776 default = expression.args.get("default") 1777 manual = expression.args.get("manual") 1778 never = expression.args.get("never") 1779 1780 if autotemp is not None: 1781 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1782 elif always: 1783 prop = "ALWAYS" 1784 elif default: 1785 prop = "DEFAULT" 1786 elif manual: 1787 prop = "MANUAL" 1788 elif never: 1789 prop = "NEVER" 1790 return f"BLOCKCOMPRESSION={prop}" 1791 1792 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1793 no = expression.args.get("no") 1794 no = " NO" if no else "" 1795 concurrent = expression.args.get("concurrent") 1796 concurrent = " CONCURRENT" if concurrent else "" 1797 target = self.sql(expression, "target") 1798 target = f" {target}" if target else "" 1799 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1800 1801 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1802 if isinstance(expression.this, list): 1803 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1804 if expression.this: 1805 modulus = self.sql(expression, "this") 1806 remainder = self.sql(expression, "expression") 1807 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1808 1809 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1810 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1811 return f"FROM ({from_expressions}) TO ({to_expressions})" 1812 1813 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1814 this = self.sql(expression, "this") 1815 1816 for_values_or_default = expression.expression 1817 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1818 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1819 else: 1820 for_values_or_default = " DEFAULT" 1821 1822 return f"PARTITION OF {this}{for_values_or_default}" 1823 1824 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1825 kind = expression.args.get("kind") 1826 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1827 for_or_in = expression.args.get("for_or_in") 1828 for_or_in = f" {for_or_in}" if for_or_in else "" 1829 lock_type = expression.args.get("lock_type") 1830 override = " OVERRIDE" if expression.args.get("override") else "" 1831 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1832 1833 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1834 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1835 statistics = expression.args.get("statistics") 1836 statistics_sql = "" 1837 if statistics is not None: 1838 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1839 return f"{data_sql}{statistics_sql}" 1840 1841 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1842 this = self.sql(expression, "this") 1843 this = f"HISTORY_TABLE={this}" if this else "" 1844 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1845 data_consistency = ( 1846 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1847 ) 1848 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1849 retention_period = ( 1850 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1851 ) 1852 1853 if this: 1854 on_sql = self.func("ON", this, data_consistency, retention_period) 1855 else: 1856 on_sql = "ON" if expression.args.get("on") else "OFF" 1857 1858 sql = f"SYSTEM_VERSIONING={on_sql}" 1859 1860 return f"WITH({sql})" if expression.args.get("with") else sql 1861 1862 def insert_sql(self, expression: exp.Insert) -> str: 1863 hint = self.sql(expression, "hint") 1864 overwrite = expression.args.get("overwrite") 1865 1866 if isinstance(expression.this, exp.Directory): 1867 this = " OVERWRITE" if overwrite else " INTO" 1868 else: 1869 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1870 1871 stored = self.sql(expression, "stored") 1872 stored = f" {stored}" if stored else "" 1873 alternative = expression.args.get("alternative") 1874 alternative = f" OR {alternative}" if alternative else "" 1875 ignore = " IGNORE" if expression.args.get("ignore") else "" 1876 is_function = expression.args.get("is_function") 1877 if is_function: 1878 this = f"{this} FUNCTION" 1879 this = f"{this} {self.sql(expression, 'this')}" 1880 1881 exists = " IF EXISTS" if expression.args.get("exists") else "" 1882 where = self.sql(expression, "where") 1883 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1884 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1885 on_conflict = self.sql(expression, "conflict") 1886 on_conflict = f" {on_conflict}" if on_conflict else "" 1887 by_name = " BY NAME" if expression.args.get("by_name") else "" 1888 returning = self.sql(expression, "returning") 1889 1890 if self.RETURNING_END: 1891 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1892 else: 1893 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1894 1895 partition_by = self.sql(expression, "partition") 1896 partition_by = f" {partition_by}" if partition_by else "" 1897 settings = self.sql(expression, "settings") 1898 settings = f" {settings}" if settings else "" 1899 1900 source = self.sql(expression, "source") 1901 source = f"TABLE {source}" if source else "" 1902 1903 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1904 return self.prepend_ctes(expression, sql) 1905 1906 def introducer_sql(self, expression: exp.Introducer) -> str: 1907 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1908 1909 def kill_sql(self, expression: exp.Kill) -> str: 1910 kind = self.sql(expression, "kind") 1911 kind = f" {kind}" if kind else "" 1912 this = self.sql(expression, "this") 1913 this = f" {this}" if this else "" 1914 return f"KILL{kind}{this}" 1915 1916 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1917 return expression.name 1918 1919 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1920 return expression.name 1921 1922 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1923 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1924 1925 constraint = self.sql(expression, "constraint") 1926 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1927 1928 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1929 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1930 action = self.sql(expression, "action") 1931 1932 expressions = self.expressions(expression, flat=True) 1933 if expressions: 1934 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1935 expressions = f" {set_keyword}{expressions}" 1936 1937 where = self.sql(expression, "where") 1938 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1939 1940 def returning_sql(self, expression: exp.Returning) -> str: 1941 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1942 1943 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1944 fields = self.sql(expression, "fields") 1945 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1946 escaped = self.sql(expression, "escaped") 1947 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1948 items = self.sql(expression, "collection_items") 1949 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1950 keys = self.sql(expression, "map_keys") 1951 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1952 lines = self.sql(expression, "lines") 1953 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1954 null = self.sql(expression, "null") 1955 null = f" NULL DEFINED AS {null}" if null else "" 1956 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1957 1958 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1959 return f"WITH ({self.expressions(expression, flat=True)})" 1960 1961 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1962 this = f"{self.sql(expression, 'this')} INDEX" 1963 target = self.sql(expression, "target") 1964 target = f" FOR {target}" if target else "" 1965 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1966 1967 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1968 this = self.sql(expression, "this") 1969 kind = self.sql(expression, "kind") 1970 expr = self.sql(expression, "expression") 1971 return f"{this} ({kind} => {expr})" 1972 1973 def table_parts(self, expression: exp.Table) -> str: 1974 return ".".join( 1975 self.sql(part) 1976 for part in ( 1977 expression.args.get("catalog"), 1978 expression.args.get("db"), 1979 expression.args.get("this"), 1980 ) 1981 if part is not None 1982 ) 1983 1984 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1985 table = self.table_parts(expression) 1986 only = "ONLY " if expression.args.get("only") else "" 1987 partition = self.sql(expression, "partition") 1988 partition = f" {partition}" if partition else "" 1989 version = self.sql(expression, "version") 1990 version = f" {version}" if version else "" 1991 alias = self.sql(expression, "alias") 1992 alias = f"{sep}{alias}" if alias else "" 1993 1994 sample = self.sql(expression, "sample") 1995 if self.dialect.ALIAS_POST_TABLESAMPLE: 1996 sample_pre_alias = sample 1997 sample_post_alias = "" 1998 else: 1999 sample_pre_alias = "" 2000 sample_post_alias = sample 2001 2002 hints = self.expressions(expression, key="hints", sep=" ") 2003 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2004 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2005 joins = self.indent( 2006 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2007 ) 2008 laterals = self.expressions(expression, key="laterals", sep="") 2009 2010 file_format = self.sql(expression, "format") 2011 if file_format: 2012 pattern = self.sql(expression, "pattern") 2013 pattern = f", PATTERN => {pattern}" if pattern else "" 2014 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2015 2016 ordinality = expression.args.get("ordinality") or "" 2017 if ordinality: 2018 ordinality = f" WITH ORDINALITY{alias}" 2019 alias = "" 2020 2021 when = self.sql(expression, "when") 2022 if when: 2023 table = f"{table} {when}" 2024 2025 changes = self.sql(expression, "changes") 2026 changes = f" {changes}" if changes else "" 2027 2028 rows_from = self.expressions(expression, key="rows_from") 2029 if rows_from: 2030 table = f"ROWS FROM {self.wrap(rows_from)}" 2031 2032 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2033 2034 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2035 table = self.func("TABLE", expression.this) 2036 alias = self.sql(expression, "alias") 2037 alias = f" AS {alias}" if alias else "" 2038 sample = self.sql(expression, "sample") 2039 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2040 joins = self.indent( 2041 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2042 ) 2043 return f"{table}{alias}{pivots}{sample}{joins}" 2044 2045 def tablesample_sql( 2046 self, 2047 expression: exp.TableSample, 2048 tablesample_keyword: t.Optional[str] = None, 2049 ) -> str: 2050 method = self.sql(expression, "method") 2051 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2052 numerator = self.sql(expression, "bucket_numerator") 2053 denominator = self.sql(expression, "bucket_denominator") 2054 field = self.sql(expression, "bucket_field") 2055 field = f" ON {field}" if field else "" 2056 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2057 seed = self.sql(expression, "seed") 2058 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2059 2060 size = self.sql(expression, "size") 2061 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2062 size = f"{size} ROWS" 2063 2064 percent = self.sql(expression, "percent") 2065 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2066 percent = f"{percent} PERCENT" 2067 2068 expr = f"{bucket}{percent}{size}" 2069 if self.TABLESAMPLE_REQUIRES_PARENS: 2070 expr = f"({expr})" 2071 2072 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2073 2074 def pivot_sql(self, expression: exp.Pivot) -> str: 2075 expressions = self.expressions(expression, flat=True) 2076 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2077 2078 group = self.sql(expression, "group") 2079 2080 if expression.this: 2081 this = self.sql(expression, "this") 2082 if not expressions: 2083 return f"UNPIVOT {this}" 2084 2085 on = f"{self.seg('ON')} {expressions}" 2086 into = self.sql(expression, "into") 2087 into = f"{self.seg('INTO')} {into}" if into else "" 2088 using = self.expressions(expression, key="using", flat=True) 2089 using = f"{self.seg('USING')} {using}" if using else "" 2090 return f"{direction} {this}{on}{into}{using}{group}" 2091 2092 alias = self.sql(expression, "alias") 2093 alias = f" AS {alias}" if alias else "" 2094 2095 fields = self.expressions( 2096 expression, 2097 "fields", 2098 sep=" ", 2099 dynamic=True, 2100 new_line=True, 2101 skip_first=True, 2102 skip_last=True, 2103 ) 2104 2105 include_nulls = expression.args.get("include_nulls") 2106 if include_nulls is not None: 2107 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2108 else: 2109 nulls = "" 2110 2111 default_on_null = self.sql(expression, "default_on_null") 2112 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2113 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2114 2115 def version_sql(self, expression: exp.Version) -> str: 2116 this = f"FOR {expression.name}" 2117 kind = expression.text("kind") 2118 expr = self.sql(expression, "expression") 2119 return f"{this} {kind} {expr}" 2120 2121 def tuple_sql(self, expression: exp.Tuple) -> str: 2122 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2123 2124 def update_sql(self, expression: exp.Update) -> str: 2125 this = self.sql(expression, "this") 2126 set_sql = self.expressions(expression, flat=True) 2127 from_sql = self.sql(expression, "from") 2128 where_sql = self.sql(expression, "where") 2129 returning = self.sql(expression, "returning") 2130 order = self.sql(expression, "order") 2131 limit = self.sql(expression, "limit") 2132 if self.RETURNING_END: 2133 expression_sql = f"{from_sql}{where_sql}{returning}" 2134 else: 2135 expression_sql = f"{returning}{from_sql}{where_sql}" 2136 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2137 return self.prepend_ctes(expression, sql) 2138 2139 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2140 values_as_table = values_as_table and self.VALUES_AS_TABLE 2141 2142 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2143 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2144 args = self.expressions(expression) 2145 alias = self.sql(expression, "alias") 2146 values = f"VALUES{self.seg('')}{args}" 2147 values = ( 2148 f"({values})" 2149 if self.WRAP_DERIVED_VALUES 2150 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2151 else values 2152 ) 2153 return f"{values} AS {alias}" if alias else values 2154 2155 # Converts `VALUES...` expression into a series of select unions. 2156 alias_node = expression.args.get("alias") 2157 column_names = alias_node and alias_node.columns 2158 2159 selects: t.List[exp.Query] = [] 2160 2161 for i, tup in enumerate(expression.expressions): 2162 row = tup.expressions 2163 2164 if i == 0 and column_names: 2165 row = [ 2166 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2167 ] 2168 2169 selects.append(exp.Select(expressions=row)) 2170 2171 if self.pretty: 2172 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2173 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2174 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2175 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2176 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2177 2178 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2179 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2180 return f"({unions}){alias}" 2181 2182 def var_sql(self, expression: exp.Var) -> str: 2183 return self.sql(expression, "this") 2184 2185 @unsupported_args("expressions") 2186 def into_sql(self, expression: exp.Into) -> str: 2187 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2188 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2189 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2190 2191 def from_sql(self, expression: exp.From) -> str: 2192 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2193 2194 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2195 grouping_sets = self.expressions(expression, indent=False) 2196 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2197 2198 def rollup_sql(self, expression: exp.Rollup) -> str: 2199 expressions = self.expressions(expression, indent=False) 2200 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2201 2202 def cube_sql(self, expression: exp.Cube) -> str: 2203 expressions = self.expressions(expression, indent=False) 2204 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2205 2206 def group_sql(self, expression: exp.Group) -> str: 2207 group_by_all = expression.args.get("all") 2208 if group_by_all is True: 2209 modifier = " ALL" 2210 elif group_by_all is False: 2211 modifier = " DISTINCT" 2212 else: 2213 modifier = "" 2214 2215 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2216 2217 grouping_sets = self.expressions(expression, key="grouping_sets") 2218 cube = self.expressions(expression, key="cube") 2219 rollup = self.expressions(expression, key="rollup") 2220 2221 groupings = csv( 2222 self.seg(grouping_sets) if grouping_sets else "", 2223 self.seg(cube) if cube else "", 2224 self.seg(rollup) if rollup else "", 2225 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2226 sep=self.GROUPINGS_SEP, 2227 ) 2228 2229 if ( 2230 expression.expressions 2231 and groupings 2232 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2233 ): 2234 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2235 2236 return f"{group_by}{groupings}" 2237 2238 def having_sql(self, expression: exp.Having) -> str: 2239 this = self.indent(self.sql(expression, "this")) 2240 return f"{self.seg('HAVING')}{self.sep()}{this}" 2241 2242 def connect_sql(self, expression: exp.Connect) -> str: 2243 start = self.sql(expression, "start") 2244 start = self.seg(f"START WITH {start}") if start else "" 2245 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2246 connect = self.sql(expression, "connect") 2247 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2248 return start + connect 2249 2250 def prior_sql(self, expression: exp.Prior) -> str: 2251 return f"PRIOR {self.sql(expression, 'this')}" 2252 2253 def join_sql(self, expression: exp.Join) -> str: 2254 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2255 side = None 2256 else: 2257 side = expression.side 2258 2259 op_sql = " ".join( 2260 op 2261 for op in ( 2262 expression.method, 2263 "GLOBAL" if expression.args.get("global") else None, 2264 side, 2265 expression.kind, 2266 expression.hint if self.JOIN_HINTS else None, 2267 ) 2268 if op 2269 ) 2270 match_cond = self.sql(expression, "match_condition") 2271 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2272 on_sql = self.sql(expression, "on") 2273 using = expression.args.get("using") 2274 2275 if not on_sql and using: 2276 on_sql = csv(*(self.sql(column) for column in using)) 2277 2278 this = expression.this 2279 this_sql = self.sql(this) 2280 2281 exprs = self.expressions(expression) 2282 if exprs: 2283 this_sql = f"{this_sql},{self.seg(exprs)}" 2284 2285 if on_sql: 2286 on_sql = self.indent(on_sql, skip_first=True) 2287 space = self.seg(" " * self.pad) if self.pretty else " " 2288 if using: 2289 on_sql = f"{space}USING ({on_sql})" 2290 else: 2291 on_sql = f"{space}ON {on_sql}" 2292 elif not op_sql: 2293 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2294 return f" {this_sql}" 2295 2296 return f", {this_sql}" 2297 2298 if op_sql != "STRAIGHT_JOIN": 2299 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2300 2301 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2302 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2303 2304 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2305 args = self.expressions(expression, flat=True) 2306 args = f"({args})" if len(args.split(",")) > 1 else args 2307 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2308 2309 def lateral_op(self, expression: exp.Lateral) -> str: 2310 cross_apply = expression.args.get("cross_apply") 2311 2312 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2313 if cross_apply is True: 2314 op = "INNER JOIN " 2315 elif cross_apply is False: 2316 op = "LEFT JOIN " 2317 else: 2318 op = "" 2319 2320 return f"{op}LATERAL" 2321 2322 def lateral_sql(self, expression: exp.Lateral) -> str: 2323 this = self.sql(expression, "this") 2324 2325 if expression.args.get("view"): 2326 alias = expression.args["alias"] 2327 columns = self.expressions(alias, key="columns", flat=True) 2328 table = f" {alias.name}" if alias.name else "" 2329 columns = f" AS {columns}" if columns else "" 2330 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2331 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2332 2333 alias = self.sql(expression, "alias") 2334 alias = f" AS {alias}" if alias else "" 2335 2336 ordinality = expression.args.get("ordinality") or "" 2337 if ordinality: 2338 ordinality = f" WITH ORDINALITY{alias}" 2339 alias = "" 2340 2341 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2342 2343 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2344 this = self.sql(expression, "this") 2345 2346 args = [ 2347 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2348 for e in (expression.args.get(k) for k in ("offset", "expression")) 2349 if e 2350 ] 2351 2352 args_sql = ", ".join(self.sql(e) for e in args) 2353 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2354 expressions = self.expressions(expression, flat=True) 2355 limit_options = self.sql(expression, "limit_options") 2356 expressions = f" BY {expressions}" if expressions else "" 2357 2358 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2359 2360 def offset_sql(self, expression: exp.Offset) -> str: 2361 this = self.sql(expression, "this") 2362 value = expression.expression 2363 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2364 expressions = self.expressions(expression, flat=True) 2365 expressions = f" BY {expressions}" if expressions else "" 2366 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2367 2368 def setitem_sql(self, expression: exp.SetItem) -> str: 2369 kind = self.sql(expression, "kind") 2370 kind = f"{kind} " if kind else "" 2371 this = self.sql(expression, "this") 2372 expressions = self.expressions(expression) 2373 collate = self.sql(expression, "collate") 2374 collate = f" COLLATE {collate}" if collate else "" 2375 global_ = "GLOBAL " if expression.args.get("global") else "" 2376 return f"{global_}{kind}{this}{expressions}{collate}" 2377 2378 def set_sql(self, expression: exp.Set) -> str: 2379 expressions = f" {self.expressions(expression, flat=True)}" 2380 tag = " TAG" if expression.args.get("tag") else "" 2381 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2382 2383 def pragma_sql(self, expression: exp.Pragma) -> str: 2384 return f"PRAGMA {self.sql(expression, 'this')}" 2385 2386 def lock_sql(self, expression: exp.Lock) -> str: 2387 if not self.LOCKING_READS_SUPPORTED: 2388 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2389 return "" 2390 2391 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2392 expressions = self.expressions(expression, flat=True) 2393 expressions = f" OF {expressions}" if expressions else "" 2394 wait = expression.args.get("wait") 2395 2396 if wait is not None: 2397 if isinstance(wait, exp.Literal): 2398 wait = f" WAIT {self.sql(wait)}" 2399 else: 2400 wait = " NOWAIT" if wait else " SKIP LOCKED" 2401 2402 return f"{lock_type}{expressions}{wait or ''}" 2403 2404 def literal_sql(self, expression: exp.Literal) -> str: 2405 text = expression.this or "" 2406 if expression.is_string: 2407 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2408 return text 2409 2410 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2411 if self.dialect.ESCAPED_SEQUENCES: 2412 to_escaped = self.dialect.ESCAPED_SEQUENCES 2413 text = "".join( 2414 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2415 ) 2416 2417 return self._replace_line_breaks(text).replace( 2418 self.dialect.QUOTE_END, self._escaped_quote_end 2419 ) 2420 2421 def loaddata_sql(self, expression: exp.LoadData) -> str: 2422 local = " LOCAL" if expression.args.get("local") else "" 2423 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2424 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2425 this = f" INTO TABLE {self.sql(expression, 'this')}" 2426 partition = self.sql(expression, "partition") 2427 partition = f" {partition}" if partition else "" 2428 input_format = self.sql(expression, "input_format") 2429 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2430 serde = self.sql(expression, "serde") 2431 serde = f" SERDE {serde}" if serde else "" 2432 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2433 2434 def null_sql(self, *_) -> str: 2435 return "NULL" 2436 2437 def boolean_sql(self, expression: exp.Boolean) -> str: 2438 return "TRUE" if expression.this else "FALSE" 2439 2440 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2441 this = self.sql(expression, "this") 2442 this = f"{this} " if this else this 2443 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2444 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2445 2446 def withfill_sql(self, expression: exp.WithFill) -> str: 2447 from_sql = self.sql(expression, "from") 2448 from_sql = f" FROM {from_sql}" if from_sql else "" 2449 to_sql = self.sql(expression, "to") 2450 to_sql = f" TO {to_sql}" if to_sql else "" 2451 step_sql = self.sql(expression, "step") 2452 step_sql = f" STEP {step_sql}" if step_sql else "" 2453 interpolated_values = [ 2454 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2455 if isinstance(e, exp.Alias) 2456 else self.sql(e, "this") 2457 for e in expression.args.get("interpolate") or [] 2458 ] 2459 interpolate = ( 2460 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2461 ) 2462 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2463 2464 def cluster_sql(self, expression: exp.Cluster) -> str: 2465 return self.op_expressions("CLUSTER BY", expression) 2466 2467 def distribute_sql(self, expression: exp.Distribute) -> str: 2468 return self.op_expressions("DISTRIBUTE BY", expression) 2469 2470 def sort_sql(self, expression: exp.Sort) -> str: 2471 return self.op_expressions("SORT BY", expression) 2472 2473 def ordered_sql(self, expression: exp.Ordered) -> str: 2474 desc = expression.args.get("desc") 2475 asc = not desc 2476 2477 nulls_first = expression.args.get("nulls_first") 2478 nulls_last = not nulls_first 2479 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2480 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2481 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2482 2483 this = self.sql(expression, "this") 2484 2485 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2486 nulls_sort_change = "" 2487 if nulls_first and ( 2488 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2489 ): 2490 nulls_sort_change = " NULLS FIRST" 2491 elif ( 2492 nulls_last 2493 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2494 and not nulls_are_last 2495 ): 2496 nulls_sort_change = " NULLS LAST" 2497 2498 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2499 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2500 window = expression.find_ancestor(exp.Window, exp.Select) 2501 if isinstance(window, exp.Window) and window.args.get("spec"): 2502 self.unsupported( 2503 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2504 ) 2505 nulls_sort_change = "" 2506 elif self.NULL_ORDERING_SUPPORTED is False and ( 2507 (asc and nulls_sort_change == " NULLS LAST") 2508 or (desc and nulls_sort_change == " NULLS FIRST") 2509 ): 2510 # BigQuery does not allow these ordering/nulls combinations when used under 2511 # an aggregation func or under a window containing one 2512 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2513 2514 if isinstance(ancestor, exp.Window): 2515 ancestor = ancestor.this 2516 if isinstance(ancestor, exp.AggFunc): 2517 self.unsupported( 2518 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2519 ) 2520 nulls_sort_change = "" 2521 elif self.NULL_ORDERING_SUPPORTED is None: 2522 if expression.this.is_int: 2523 self.unsupported( 2524 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2525 ) 2526 elif not isinstance(expression.this, exp.Rand): 2527 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2528 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2529 nulls_sort_change = "" 2530 2531 with_fill = self.sql(expression, "with_fill") 2532 with_fill = f" {with_fill}" if with_fill else "" 2533 2534 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2535 2536 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2537 window_frame = self.sql(expression, "window_frame") 2538 window_frame = f"{window_frame} " if window_frame else "" 2539 2540 this = self.sql(expression, "this") 2541 2542 return f"{window_frame}{this}" 2543 2544 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2545 partition = self.partition_by_sql(expression) 2546 order = self.sql(expression, "order") 2547 measures = self.expressions(expression, key="measures") 2548 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2549 rows = self.sql(expression, "rows") 2550 rows = self.seg(rows) if rows else "" 2551 after = self.sql(expression, "after") 2552 after = self.seg(after) if after else "" 2553 pattern = self.sql(expression, "pattern") 2554 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2555 definition_sqls = [ 2556 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2557 for definition in expression.args.get("define", []) 2558 ] 2559 definitions = self.expressions(sqls=definition_sqls) 2560 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2561 body = "".join( 2562 ( 2563 partition, 2564 order, 2565 measures, 2566 rows, 2567 after, 2568 pattern, 2569 define, 2570 ) 2571 ) 2572 alias = self.sql(expression, "alias") 2573 alias = f" {alias}" if alias else "" 2574 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2575 2576 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2577 limit = expression.args.get("limit") 2578 2579 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2580 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2581 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2582 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2583 2584 return csv( 2585 *sqls, 2586 *[self.sql(join) for join in expression.args.get("joins") or []], 2587 self.sql(expression, "match"), 2588 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2589 self.sql(expression, "prewhere"), 2590 self.sql(expression, "where"), 2591 self.sql(expression, "connect"), 2592 self.sql(expression, "group"), 2593 self.sql(expression, "having"), 2594 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2595 self.sql(expression, "order"), 2596 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2597 *self.after_limit_modifiers(expression), 2598 self.options_modifier(expression), 2599 sep="", 2600 ) 2601 2602 def options_modifier(self, expression: exp.Expression) -> str: 2603 options = self.expressions(expression, key="options") 2604 return f" {options}" if options else "" 2605 2606 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2607 self.unsupported("Unsupported query option.") 2608 return "" 2609 2610 def offset_limit_modifiers( 2611 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2612 ) -> t.List[str]: 2613 return [ 2614 self.sql(expression, "offset") if fetch else self.sql(limit), 2615 self.sql(limit) if fetch else self.sql(expression, "offset"), 2616 ] 2617 2618 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2619 locks = self.expressions(expression, key="locks", sep=" ") 2620 locks = f" {locks}" if locks else "" 2621 return [locks, self.sql(expression, "sample")] 2622 2623 def select_sql(self, expression: exp.Select) -> str: 2624 into = expression.args.get("into") 2625 if not self.SUPPORTS_SELECT_INTO and into: 2626 into.pop() 2627 2628 hint = self.sql(expression, "hint") 2629 distinct = self.sql(expression, "distinct") 2630 distinct = f" {distinct}" if distinct else "" 2631 kind = self.sql(expression, "kind") 2632 2633 limit = expression.args.get("limit") 2634 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2635 top = self.limit_sql(limit, top=True) 2636 limit.pop() 2637 else: 2638 top = "" 2639 2640 expressions = self.expressions(expression) 2641 2642 if kind: 2643 if kind in self.SELECT_KINDS: 2644 kind = f" AS {kind}" 2645 else: 2646 if kind == "STRUCT": 2647 expressions = self.expressions( 2648 sqls=[ 2649 self.sql( 2650 exp.Struct( 2651 expressions=[ 2652 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2653 if isinstance(e, exp.Alias) 2654 else e 2655 for e in expression.expressions 2656 ] 2657 ) 2658 ) 2659 ] 2660 ) 2661 kind = "" 2662 2663 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2664 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2665 2666 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2667 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2668 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2669 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2670 sql = self.query_modifiers( 2671 expression, 2672 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2673 self.sql(expression, "into", comment=False), 2674 self.sql(expression, "from", comment=False), 2675 ) 2676 2677 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2678 if expression.args.get("with"): 2679 sql = self.maybe_comment(sql, expression) 2680 expression.pop_comments() 2681 2682 sql = self.prepend_ctes(expression, sql) 2683 2684 if not self.SUPPORTS_SELECT_INTO and into: 2685 if into.args.get("temporary"): 2686 table_kind = " TEMPORARY" 2687 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2688 table_kind = " UNLOGGED" 2689 else: 2690 table_kind = "" 2691 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2692 2693 return sql 2694 2695 def schema_sql(self, expression: exp.Schema) -> str: 2696 this = self.sql(expression, "this") 2697 sql = self.schema_columns_sql(expression) 2698 return f"{this} {sql}" if this and sql else this or sql 2699 2700 def schema_columns_sql(self, expression: exp.Schema) -> str: 2701 if expression.expressions: 2702 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2703 return "" 2704 2705 def star_sql(self, expression: exp.Star) -> str: 2706 except_ = self.expressions(expression, key="except", flat=True) 2707 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2708 replace = self.expressions(expression, key="replace", flat=True) 2709 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2710 rename = self.expressions(expression, key="rename", flat=True) 2711 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2712 return f"*{except_}{replace}{rename}" 2713 2714 def parameter_sql(self, expression: exp.Parameter) -> str: 2715 this = self.sql(expression, "this") 2716 return f"{self.PARAMETER_TOKEN}{this}" 2717 2718 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2719 this = self.sql(expression, "this") 2720 kind = expression.text("kind") 2721 if kind: 2722 kind = f"{kind}." 2723 return f"@@{kind}{this}" 2724 2725 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2726 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2727 2728 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2729 alias = self.sql(expression, "alias") 2730 alias = f"{sep}{alias}" if alias else "" 2731 sample = self.sql(expression, "sample") 2732 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2733 alias = f"{sample}{alias}" 2734 2735 # Set to None so it's not generated again by self.query_modifiers() 2736 expression.set("sample", None) 2737 2738 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2739 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2740 return self.prepend_ctes(expression, sql) 2741 2742 def qualify_sql(self, expression: exp.Qualify) -> str: 2743 this = self.indent(self.sql(expression, "this")) 2744 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2745 2746 def unnest_sql(self, expression: exp.Unnest) -> str: 2747 args = self.expressions(expression, flat=True) 2748 2749 alias = expression.args.get("alias") 2750 offset = expression.args.get("offset") 2751 2752 if self.UNNEST_WITH_ORDINALITY: 2753 if alias and isinstance(offset, exp.Expression): 2754 alias.append("columns", offset) 2755 2756 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2757 columns = alias.columns 2758 alias = self.sql(columns[0]) if columns else "" 2759 else: 2760 alias = self.sql(alias) 2761 2762 alias = f" AS {alias}" if alias else alias 2763 if self.UNNEST_WITH_ORDINALITY: 2764 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2765 else: 2766 if isinstance(offset, exp.Expression): 2767 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2768 elif offset: 2769 suffix = f"{alias} WITH OFFSET" 2770 else: 2771 suffix = alias 2772 2773 return f"UNNEST({args}){suffix}" 2774 2775 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2776 return "" 2777 2778 def where_sql(self, expression: exp.Where) -> str: 2779 this = self.indent(self.sql(expression, "this")) 2780 return f"{self.seg('WHERE')}{self.sep()}{this}" 2781 2782 def window_sql(self, expression: exp.Window) -> str: 2783 this = self.sql(expression, "this") 2784 partition = self.partition_by_sql(expression) 2785 order = expression.args.get("order") 2786 order = self.order_sql(order, flat=True) if order else "" 2787 spec = self.sql(expression, "spec") 2788 alias = self.sql(expression, "alias") 2789 over = self.sql(expression, "over") or "OVER" 2790 2791 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2792 2793 first = expression.args.get("first") 2794 if first is None: 2795 first = "" 2796 else: 2797 first = "FIRST" if first else "LAST" 2798 2799 if not partition and not order and not spec and alias: 2800 return f"{this} {alias}" 2801 2802 args = self.format_args( 2803 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2804 ) 2805 return f"{this} ({args})" 2806 2807 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2808 partition = self.expressions(expression, key="partition_by", flat=True) 2809 return f"PARTITION BY {partition}" if partition else "" 2810 2811 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2812 kind = self.sql(expression, "kind") 2813 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2814 end = ( 2815 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2816 or "CURRENT ROW" 2817 ) 2818 2819 window_spec = f"{kind} BETWEEN {start} AND {end}" 2820 2821 exclude = self.sql(expression, "exclude") 2822 if exclude: 2823 if self.SUPPORTS_WINDOW_EXCLUDE: 2824 window_spec += f" EXCLUDE {exclude}" 2825 else: 2826 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2827 2828 return window_spec 2829 2830 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2831 this = self.sql(expression, "this") 2832 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2833 return f"{this} WITHIN GROUP ({expression_sql})" 2834 2835 def between_sql(self, expression: exp.Between) -> str: 2836 this = self.sql(expression, "this") 2837 low = self.sql(expression, "low") 2838 high = self.sql(expression, "high") 2839 return f"{this} BETWEEN {low} AND {high}" 2840 2841 def bracket_offset_expressions( 2842 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2843 ) -> t.List[exp.Expression]: 2844 return apply_index_offset( 2845 expression.this, 2846 expression.expressions, 2847 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2848 dialect=self.dialect, 2849 ) 2850 2851 def bracket_sql(self, expression: exp.Bracket) -> str: 2852 expressions = self.bracket_offset_expressions(expression) 2853 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2854 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2855 2856 def all_sql(self, expression: exp.All) -> str: 2857 return f"ALL {self.wrap(expression)}" 2858 2859 def any_sql(self, expression: exp.Any) -> str: 2860 this = self.sql(expression, "this") 2861 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2862 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2863 this = self.wrap(this) 2864 return f"ANY{this}" 2865 return f"ANY {this}" 2866 2867 def exists_sql(self, expression: exp.Exists) -> str: 2868 return f"EXISTS{self.wrap(expression)}" 2869 2870 def case_sql(self, expression: exp.Case) -> str: 2871 this = self.sql(expression, "this") 2872 statements = [f"CASE {this}" if this else "CASE"] 2873 2874 for e in expression.args["ifs"]: 2875 statements.append(f"WHEN {self.sql(e, 'this')}") 2876 statements.append(f"THEN {self.sql(e, 'true')}") 2877 2878 default = self.sql(expression, "default") 2879 2880 if default: 2881 statements.append(f"ELSE {default}") 2882 2883 statements.append("END") 2884 2885 if self.pretty and self.too_wide(statements): 2886 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2887 2888 return " ".join(statements) 2889 2890 def constraint_sql(self, expression: exp.Constraint) -> str: 2891 this = self.sql(expression, "this") 2892 expressions = self.expressions(expression, flat=True) 2893 return f"CONSTRAINT {this} {expressions}" 2894 2895 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2896 order = expression.args.get("order") 2897 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2898 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2899 2900 def extract_sql(self, expression: exp.Extract) -> str: 2901 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2902 expression_sql = self.sql(expression, "expression") 2903 return f"EXTRACT({this} FROM {expression_sql})" 2904 2905 def trim_sql(self, expression: exp.Trim) -> str: 2906 trim_type = self.sql(expression, "position") 2907 2908 if trim_type == "LEADING": 2909 func_name = "LTRIM" 2910 elif trim_type == "TRAILING": 2911 func_name = "RTRIM" 2912 else: 2913 func_name = "TRIM" 2914 2915 return self.func(func_name, expression.this, expression.expression) 2916 2917 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2918 args = expression.expressions 2919 if isinstance(expression, exp.ConcatWs): 2920 args = args[1:] # Skip the delimiter 2921 2922 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2923 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2924 2925 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2926 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2927 2928 return args 2929 2930 def concat_sql(self, expression: exp.Concat) -> str: 2931 expressions = self.convert_concat_args(expression) 2932 2933 # Some dialects don't allow a single-argument CONCAT call 2934 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2935 return self.sql(expressions[0]) 2936 2937 return self.func("CONCAT", *expressions) 2938 2939 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2940 return self.func( 2941 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2942 ) 2943 2944 def check_sql(self, expression: exp.Check) -> str: 2945 this = self.sql(expression, key="this") 2946 return f"CHECK ({this})" 2947 2948 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2949 expressions = self.expressions(expression, flat=True) 2950 expressions = f" ({expressions})" if expressions else "" 2951 reference = self.sql(expression, "reference") 2952 reference = f" {reference}" if reference else "" 2953 delete = self.sql(expression, "delete") 2954 delete = f" ON DELETE {delete}" if delete else "" 2955 update = self.sql(expression, "update") 2956 update = f" ON UPDATE {update}" if update else "" 2957 options = self.expressions(expression, key="options", flat=True, sep=" ") 2958 options = f" {options}" if options else "" 2959 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 2960 2961 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2962 expressions = self.expressions(expression, flat=True) 2963 options = self.expressions(expression, key="options", flat=True, sep=" ") 2964 options = f" {options}" if options else "" 2965 return f"PRIMARY KEY ({expressions}){options}" 2966 2967 def if_sql(self, expression: exp.If) -> str: 2968 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2969 2970 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2971 modifier = expression.args.get("modifier") 2972 modifier = f" {modifier}" if modifier else "" 2973 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2974 2975 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2976 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2977 2978 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2979 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2980 2981 if expression.args.get("escape"): 2982 path = self.escape_str(path) 2983 2984 if self.QUOTE_JSON_PATH: 2985 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2986 2987 return path 2988 2989 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2990 if isinstance(expression, exp.JSONPathPart): 2991 transform = self.TRANSFORMS.get(expression.__class__) 2992 if not callable(transform): 2993 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2994 return "" 2995 2996 return transform(self, expression) 2997 2998 if isinstance(expression, int): 2999 return str(expression) 3000 3001 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3002 escaped = expression.replace("'", "\\'") 3003 escaped = f"\\'{expression}\\'" 3004 else: 3005 escaped = expression.replace('"', '\\"') 3006 escaped = f'"{escaped}"' 3007 3008 return escaped 3009 3010 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3011 return f"{self.sql(expression, 'this')} FORMAT JSON" 3012 3013 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3014 null_handling = expression.args.get("null_handling") 3015 null_handling = f" {null_handling}" if null_handling else "" 3016 3017 unique_keys = expression.args.get("unique_keys") 3018 if unique_keys is not None: 3019 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3020 else: 3021 unique_keys = "" 3022 3023 return_type = self.sql(expression, "return_type") 3024 return_type = f" RETURNING {return_type}" if return_type else "" 3025 encoding = self.sql(expression, "encoding") 3026 encoding = f" ENCODING {encoding}" if encoding else "" 3027 3028 return self.func( 3029 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3030 *expression.expressions, 3031 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3032 ) 3033 3034 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3035 return self.jsonobject_sql(expression) 3036 3037 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3038 null_handling = expression.args.get("null_handling") 3039 null_handling = f" {null_handling}" if null_handling else "" 3040 return_type = self.sql(expression, "return_type") 3041 return_type = f" RETURNING {return_type}" if return_type else "" 3042 strict = " STRICT" if expression.args.get("strict") else "" 3043 return self.func( 3044 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3045 ) 3046 3047 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3048 this = self.sql(expression, "this") 3049 order = self.sql(expression, "order") 3050 null_handling = expression.args.get("null_handling") 3051 null_handling = f" {null_handling}" if null_handling else "" 3052 return_type = self.sql(expression, "return_type") 3053 return_type = f" RETURNING {return_type}" if return_type else "" 3054 strict = " STRICT" if expression.args.get("strict") else "" 3055 return self.func( 3056 "JSON_ARRAYAGG", 3057 this, 3058 suffix=f"{order}{null_handling}{return_type}{strict})", 3059 ) 3060 3061 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3062 path = self.sql(expression, "path") 3063 path = f" PATH {path}" if path else "" 3064 nested_schema = self.sql(expression, "nested_schema") 3065 3066 if nested_schema: 3067 return f"NESTED{path} {nested_schema}" 3068 3069 this = self.sql(expression, "this") 3070 kind = self.sql(expression, "kind") 3071 kind = f" {kind}" if kind else "" 3072 return f"{this}{kind}{path}" 3073 3074 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3075 return self.func("COLUMNS", *expression.expressions) 3076 3077 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3078 this = self.sql(expression, "this") 3079 path = self.sql(expression, "path") 3080 path = f", {path}" if path else "" 3081 error_handling = expression.args.get("error_handling") 3082 error_handling = f" {error_handling}" if error_handling else "" 3083 empty_handling = expression.args.get("empty_handling") 3084 empty_handling = f" {empty_handling}" if empty_handling else "" 3085 schema = self.sql(expression, "schema") 3086 return self.func( 3087 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3088 ) 3089 3090 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3091 this = self.sql(expression, "this") 3092 kind = self.sql(expression, "kind") 3093 path = self.sql(expression, "path") 3094 path = f" {path}" if path else "" 3095 as_json = " AS JSON" if expression.args.get("as_json") else "" 3096 return f"{this} {kind}{path}{as_json}" 3097 3098 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3099 this = self.sql(expression, "this") 3100 path = self.sql(expression, "path") 3101 path = f", {path}" if path else "" 3102 expressions = self.expressions(expression) 3103 with_ = ( 3104 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3105 if expressions 3106 else "" 3107 ) 3108 return f"OPENJSON({this}{path}){with_}" 3109 3110 def in_sql(self, expression: exp.In) -> str: 3111 query = expression.args.get("query") 3112 unnest = expression.args.get("unnest") 3113 field = expression.args.get("field") 3114 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3115 3116 if query: 3117 in_sql = self.sql(query) 3118 elif unnest: 3119 in_sql = self.in_unnest_op(unnest) 3120 elif field: 3121 in_sql = self.sql(field) 3122 else: 3123 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3124 3125 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3126 3127 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3128 return f"(SELECT {self.sql(unnest)})" 3129 3130 def interval_sql(self, expression: exp.Interval) -> str: 3131 unit = self.sql(expression, "unit") 3132 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3133 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3134 unit = f" {unit}" if unit else "" 3135 3136 if self.SINGLE_STRING_INTERVAL: 3137 this = expression.this.name if expression.this else "" 3138 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3139 3140 this = self.sql(expression, "this") 3141 if this: 3142 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3143 this = f" {this}" if unwrapped else f" ({this})" 3144 3145 return f"INTERVAL{this}{unit}" 3146 3147 def return_sql(self, expression: exp.Return) -> str: 3148 return f"RETURN {self.sql(expression, 'this')}" 3149 3150 def reference_sql(self, expression: exp.Reference) -> str: 3151 this = self.sql(expression, "this") 3152 expressions = self.expressions(expression, flat=True) 3153 expressions = f"({expressions})" if expressions else "" 3154 options = self.expressions(expression, key="options", flat=True, sep=" ") 3155 options = f" {options}" if options else "" 3156 return f"REFERENCES {this}{expressions}{options}" 3157 3158 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3159 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3160 parent = expression.parent 3161 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3162 return self.func( 3163 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3164 ) 3165 3166 def paren_sql(self, expression: exp.Paren) -> str: 3167 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3168 return f"({sql}{self.seg(')', sep='')}" 3169 3170 def neg_sql(self, expression: exp.Neg) -> str: 3171 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3172 this_sql = self.sql(expression, "this") 3173 sep = " " if this_sql[0] == "-" else "" 3174 return f"-{sep}{this_sql}" 3175 3176 def not_sql(self, expression: exp.Not) -> str: 3177 return f"NOT {self.sql(expression, 'this')}" 3178 3179 def alias_sql(self, expression: exp.Alias) -> str: 3180 alias = self.sql(expression, "alias") 3181 alias = f" AS {alias}" if alias else "" 3182 return f"{self.sql(expression, 'this')}{alias}" 3183 3184 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3185 alias = expression.args["alias"] 3186 3187 parent = expression.parent 3188 pivot = parent and parent.parent 3189 3190 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3191 identifier_alias = isinstance(alias, exp.Identifier) 3192 literal_alias = isinstance(alias, exp.Literal) 3193 3194 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3195 alias.replace(exp.Literal.string(alias.output_name)) 3196 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3197 alias.replace(exp.to_identifier(alias.output_name)) 3198 3199 return self.alias_sql(expression) 3200 3201 def aliases_sql(self, expression: exp.Aliases) -> str: 3202 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3203 3204 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3205 this = self.sql(expression, "this") 3206 index = self.sql(expression, "expression") 3207 return f"{this} AT {index}" 3208 3209 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3210 this = self.sql(expression, "this") 3211 zone = self.sql(expression, "zone") 3212 return f"{this} AT TIME ZONE {zone}" 3213 3214 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3215 this = self.sql(expression, "this") 3216 zone = self.sql(expression, "zone") 3217 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3218 3219 def add_sql(self, expression: exp.Add) -> str: 3220 return self.binary(expression, "+") 3221 3222 def and_sql( 3223 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3224 ) -> str: 3225 return self.connector_sql(expression, "AND", stack) 3226 3227 def or_sql( 3228 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3229 ) -> str: 3230 return self.connector_sql(expression, "OR", stack) 3231 3232 def xor_sql( 3233 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3234 ) -> str: 3235 return self.connector_sql(expression, "XOR", stack) 3236 3237 def connector_sql( 3238 self, 3239 expression: exp.Connector, 3240 op: str, 3241 stack: t.Optional[t.List[str | exp.Expression]] = None, 3242 ) -> str: 3243 if stack is not None: 3244 if expression.expressions: 3245 stack.append(self.expressions(expression, sep=f" {op} ")) 3246 else: 3247 stack.append(expression.right) 3248 if expression.comments and self.comments: 3249 for comment in expression.comments: 3250 if comment: 3251 op += f" /*{self.pad_comment(comment)}*/" 3252 stack.extend((op, expression.left)) 3253 return op 3254 3255 stack = [expression] 3256 sqls: t.List[str] = [] 3257 ops = set() 3258 3259 while stack: 3260 node = stack.pop() 3261 if isinstance(node, exp.Connector): 3262 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3263 else: 3264 sql = self.sql(node) 3265 if sqls and sqls[-1] in ops: 3266 sqls[-1] += f" {sql}" 3267 else: 3268 sqls.append(sql) 3269 3270 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3271 return sep.join(sqls) 3272 3273 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3274 return self.binary(expression, "&") 3275 3276 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3277 return self.binary(expression, "<<") 3278 3279 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3280 return f"~{self.sql(expression, 'this')}" 3281 3282 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3283 return self.binary(expression, "|") 3284 3285 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3286 return self.binary(expression, ">>") 3287 3288 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3289 return self.binary(expression, "^") 3290 3291 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3292 format_sql = self.sql(expression, "format") 3293 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3294 to_sql = self.sql(expression, "to") 3295 to_sql = f" {to_sql}" if to_sql else "" 3296 action = self.sql(expression, "action") 3297 action = f" {action}" if action else "" 3298 default = self.sql(expression, "default") 3299 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3300 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3301 3302 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3303 zone = self.sql(expression, "this") 3304 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3305 3306 def collate_sql(self, expression: exp.Collate) -> str: 3307 if self.COLLATE_IS_FUNC: 3308 return self.function_fallback_sql(expression) 3309 return self.binary(expression, "COLLATE") 3310 3311 def command_sql(self, expression: exp.Command) -> str: 3312 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3313 3314 def comment_sql(self, expression: exp.Comment) -> str: 3315 this = self.sql(expression, "this") 3316 kind = expression.args["kind"] 3317 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3318 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3319 expression_sql = self.sql(expression, "expression") 3320 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3321 3322 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3323 this = self.sql(expression, "this") 3324 delete = " DELETE" if expression.args.get("delete") else "" 3325 recompress = self.sql(expression, "recompress") 3326 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3327 to_disk = self.sql(expression, "to_disk") 3328 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3329 to_volume = self.sql(expression, "to_volume") 3330 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3331 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3332 3333 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3334 where = self.sql(expression, "where") 3335 group = self.sql(expression, "group") 3336 aggregates = self.expressions(expression, key="aggregates") 3337 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3338 3339 if not (where or group or aggregates) and len(expression.expressions) == 1: 3340 return f"TTL {self.expressions(expression, flat=True)}" 3341 3342 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3343 3344 def transaction_sql(self, expression: exp.Transaction) -> str: 3345 return "BEGIN" 3346 3347 def commit_sql(self, expression: exp.Commit) -> str: 3348 chain = expression.args.get("chain") 3349 if chain is not None: 3350 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3351 3352 return f"COMMIT{chain or ''}" 3353 3354 def rollback_sql(self, expression: exp.Rollback) -> str: 3355 savepoint = expression.args.get("savepoint") 3356 savepoint = f" TO {savepoint}" if savepoint else "" 3357 return f"ROLLBACK{savepoint}" 3358 3359 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3360 this = self.sql(expression, "this") 3361 3362 dtype = self.sql(expression, "dtype") 3363 if dtype: 3364 collate = self.sql(expression, "collate") 3365 collate = f" COLLATE {collate}" if collate else "" 3366 using = self.sql(expression, "using") 3367 using = f" USING {using}" if using else "" 3368 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3369 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3370 3371 default = self.sql(expression, "default") 3372 if default: 3373 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3374 3375 comment = self.sql(expression, "comment") 3376 if comment: 3377 return f"ALTER COLUMN {this} COMMENT {comment}" 3378 3379 visible = expression.args.get("visible") 3380 if visible: 3381 return f"ALTER COLUMN {this} SET {visible}" 3382 3383 allow_null = expression.args.get("allow_null") 3384 drop = expression.args.get("drop") 3385 3386 if not drop and not allow_null: 3387 self.unsupported("Unsupported ALTER COLUMN syntax") 3388 3389 if allow_null is not None: 3390 keyword = "DROP" if drop else "SET" 3391 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3392 3393 return f"ALTER COLUMN {this} DROP DEFAULT" 3394 3395 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3396 this = self.sql(expression, "this") 3397 3398 visible = expression.args.get("visible") 3399 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3400 3401 return f"ALTER INDEX {this} {visible_sql}" 3402 3403 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3404 this = self.sql(expression, "this") 3405 if not isinstance(expression.this, exp.Var): 3406 this = f"KEY DISTKEY {this}" 3407 return f"ALTER DISTSTYLE {this}" 3408 3409 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3410 compound = " COMPOUND" if expression.args.get("compound") else "" 3411 this = self.sql(expression, "this") 3412 expressions = self.expressions(expression, flat=True) 3413 expressions = f"({expressions})" if expressions else "" 3414 return f"ALTER{compound} SORTKEY {this or expressions}" 3415 3416 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3417 if not self.RENAME_TABLE_WITH_DB: 3418 # Remove db from tables 3419 expression = expression.transform( 3420 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3421 ).assert_is(exp.AlterRename) 3422 this = self.sql(expression, "this") 3423 return f"RENAME TO {this}" 3424 3425 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3426 exists = " IF EXISTS" if expression.args.get("exists") else "" 3427 old_column = self.sql(expression, "this") 3428 new_column = self.sql(expression, "to") 3429 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3430 3431 def alterset_sql(self, expression: exp.AlterSet) -> str: 3432 exprs = self.expressions(expression, flat=True) 3433 return f"SET {exprs}" 3434 3435 def alter_sql(self, expression: exp.Alter) -> str: 3436 actions = expression.args["actions"] 3437 3438 if isinstance(actions[0], exp.ColumnDef): 3439 actions = self.add_column_sql(expression) 3440 elif isinstance(actions[0], exp.Schema): 3441 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3442 elif isinstance(actions[0], exp.Delete): 3443 actions = self.expressions(expression, key="actions", flat=True) 3444 elif isinstance(actions[0], exp.Query): 3445 actions = "AS " + self.expressions(expression, key="actions") 3446 else: 3447 actions = self.expressions(expression, key="actions", flat=True) 3448 3449 exists = " IF EXISTS" if expression.args.get("exists") else "" 3450 on_cluster = self.sql(expression, "cluster") 3451 on_cluster = f" {on_cluster}" if on_cluster else "" 3452 only = " ONLY" if expression.args.get("only") else "" 3453 options = self.expressions(expression, key="options") 3454 options = f", {options}" if options else "" 3455 kind = self.sql(expression, "kind") 3456 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3457 3458 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}" 3459 3460 def add_column_sql(self, expression: exp.Alter) -> str: 3461 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3462 return self.expressions( 3463 expression, 3464 key="actions", 3465 prefix="ADD COLUMN ", 3466 skip_first=True, 3467 ) 3468 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3469 3470 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3471 expressions = self.expressions(expression) 3472 exists = " IF EXISTS " if expression.args.get("exists") else " " 3473 return f"DROP{exists}{expressions}" 3474 3475 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3476 return f"ADD {self.expressions(expression)}" 3477 3478 def distinct_sql(self, expression: exp.Distinct) -> str: 3479 this = self.expressions(expression, flat=True) 3480 3481 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3482 case = exp.case() 3483 for arg in expression.expressions: 3484 case = case.when(arg.is_(exp.null()), exp.null()) 3485 this = self.sql(case.else_(f"({this})")) 3486 3487 this = f" {this}" if this else "" 3488 3489 on = self.sql(expression, "on") 3490 on = f" ON {on}" if on else "" 3491 return f"DISTINCT{this}{on}" 3492 3493 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3494 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3495 3496 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3497 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3498 3499 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3500 this_sql = self.sql(expression, "this") 3501 expression_sql = self.sql(expression, "expression") 3502 kind = "MAX" if expression.args.get("max") else "MIN" 3503 return f"{this_sql} HAVING {kind} {expression_sql}" 3504 3505 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3506 return self.sql( 3507 exp.Cast( 3508 this=exp.Div(this=expression.this, expression=expression.expression), 3509 to=exp.DataType(this=exp.DataType.Type.INT), 3510 ) 3511 ) 3512 3513 def dpipe_sql(self, expression: exp.DPipe) -> str: 3514 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3515 return self.func( 3516 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3517 ) 3518 return self.binary(expression, "||") 3519 3520 def div_sql(self, expression: exp.Div) -> str: 3521 l, r = expression.left, expression.right 3522 3523 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3524 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3525 3526 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3527 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3528 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3529 3530 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3531 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3532 return self.sql( 3533 exp.cast( 3534 l / r, 3535 to=exp.DataType.Type.BIGINT, 3536 ) 3537 ) 3538 3539 return self.binary(expression, "/") 3540 3541 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3542 n = exp._wrap(expression.this, exp.Binary) 3543 d = exp._wrap(expression.expression, exp.Binary) 3544 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3545 3546 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3547 return self.binary(expression, "OVERLAPS") 3548 3549 def distance_sql(self, expression: exp.Distance) -> str: 3550 return self.binary(expression, "<->") 3551 3552 def dot_sql(self, expression: exp.Dot) -> str: 3553 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3554 3555 def eq_sql(self, expression: exp.EQ) -> str: 3556 return self.binary(expression, "=") 3557 3558 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3559 return self.binary(expression, ":=") 3560 3561 def escape_sql(self, expression: exp.Escape) -> str: 3562 return self.binary(expression, "ESCAPE") 3563 3564 def glob_sql(self, expression: exp.Glob) -> str: 3565 return self.binary(expression, "GLOB") 3566 3567 def gt_sql(self, expression: exp.GT) -> str: 3568 return self.binary(expression, ">") 3569 3570 def gte_sql(self, expression: exp.GTE) -> str: 3571 return self.binary(expression, ">=") 3572 3573 def ilike_sql(self, expression: exp.ILike) -> str: 3574 return self.binary(expression, "ILIKE") 3575 3576 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3577 return self.binary(expression, "ILIKE ANY") 3578 3579 def is_sql(self, expression: exp.Is) -> str: 3580 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3581 return self.sql( 3582 expression.this if expression.expression.this else exp.not_(expression.this) 3583 ) 3584 return self.binary(expression, "IS") 3585 3586 def like_sql(self, expression: exp.Like) -> str: 3587 return self.binary(expression, "LIKE") 3588 3589 def likeany_sql(self, expression: exp.LikeAny) -> str: 3590 return self.binary(expression, "LIKE ANY") 3591 3592 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3593 return self.binary(expression, "SIMILAR TO") 3594 3595 def lt_sql(self, expression: exp.LT) -> str: 3596 return self.binary(expression, "<") 3597 3598 def lte_sql(self, expression: exp.LTE) -> str: 3599 return self.binary(expression, "<=") 3600 3601 def mod_sql(self, expression: exp.Mod) -> str: 3602 return self.binary(expression, "%") 3603 3604 def mul_sql(self, expression: exp.Mul) -> str: 3605 return self.binary(expression, "*") 3606 3607 def neq_sql(self, expression: exp.NEQ) -> str: 3608 return self.binary(expression, "<>") 3609 3610 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3611 return self.binary(expression, "IS NOT DISTINCT FROM") 3612 3613 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3614 return self.binary(expression, "IS DISTINCT FROM") 3615 3616 def slice_sql(self, expression: exp.Slice) -> str: 3617 return self.binary(expression, ":") 3618 3619 def sub_sql(self, expression: exp.Sub) -> str: 3620 return self.binary(expression, "-") 3621 3622 def trycast_sql(self, expression: exp.TryCast) -> str: 3623 return self.cast_sql(expression, safe_prefix="TRY_") 3624 3625 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3626 return self.cast_sql(expression) 3627 3628 def try_sql(self, expression: exp.Try) -> str: 3629 if not self.TRY_SUPPORTED: 3630 self.unsupported("Unsupported TRY function") 3631 return self.sql(expression, "this") 3632 3633 return self.func("TRY", expression.this) 3634 3635 def log_sql(self, expression: exp.Log) -> str: 3636 this = expression.this 3637 expr = expression.expression 3638 3639 if self.dialect.LOG_BASE_FIRST is False: 3640 this, expr = expr, this 3641 elif self.dialect.LOG_BASE_FIRST is None and expr: 3642 if this.name in ("2", "10"): 3643 return self.func(f"LOG{this.name}", expr) 3644 3645 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3646 3647 return self.func("LOG", this, expr) 3648 3649 def use_sql(self, expression: exp.Use) -> str: 3650 kind = self.sql(expression, "kind") 3651 kind = f" {kind}" if kind else "" 3652 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3653 this = f" {this}" if this else "" 3654 return f"USE{kind}{this}" 3655 3656 def binary(self, expression: exp.Binary, op: str) -> str: 3657 sqls: t.List[str] = [] 3658 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3659 binary_type = type(expression) 3660 3661 while stack: 3662 node = stack.pop() 3663 3664 if type(node) is binary_type: 3665 op_func = node.args.get("operator") 3666 if op_func: 3667 op = f"OPERATOR({self.sql(op_func)})" 3668 3669 stack.append(node.right) 3670 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3671 stack.append(node.left) 3672 else: 3673 sqls.append(self.sql(node)) 3674 3675 return "".join(sqls) 3676 3677 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3678 to_clause = self.sql(expression, "to") 3679 if to_clause: 3680 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3681 3682 return self.function_fallback_sql(expression) 3683 3684 def function_fallback_sql(self, expression: exp.Func) -> str: 3685 args = [] 3686 3687 for key in expression.arg_types: 3688 arg_value = expression.args.get(key) 3689 3690 if isinstance(arg_value, list): 3691 for value in arg_value: 3692 args.append(value) 3693 elif arg_value is not None: 3694 args.append(arg_value) 3695 3696 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3697 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3698 else: 3699 name = expression.sql_name() 3700 3701 return self.func(name, *args) 3702 3703 def func( 3704 self, 3705 name: str, 3706 *args: t.Optional[exp.Expression | str], 3707 prefix: str = "(", 3708 suffix: str = ")", 3709 normalize: bool = True, 3710 ) -> str: 3711 name = self.normalize_func(name) if normalize else name 3712 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3713 3714 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3715 arg_sqls = tuple( 3716 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3717 ) 3718 if self.pretty and self.too_wide(arg_sqls): 3719 return self.indent( 3720 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3721 ) 3722 return sep.join(arg_sqls) 3723 3724 def too_wide(self, args: t.Iterable) -> bool: 3725 return sum(len(arg) for arg in args) > self.max_text_width 3726 3727 def format_time( 3728 self, 3729 expression: exp.Expression, 3730 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3731 inverse_time_trie: t.Optional[t.Dict] = None, 3732 ) -> t.Optional[str]: 3733 return format_time( 3734 self.sql(expression, "format"), 3735 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3736 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3737 ) 3738 3739 def expressions( 3740 self, 3741 expression: t.Optional[exp.Expression] = None, 3742 key: t.Optional[str] = None, 3743 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3744 flat: bool = False, 3745 indent: bool = True, 3746 skip_first: bool = False, 3747 skip_last: bool = False, 3748 sep: str = ", ", 3749 prefix: str = "", 3750 dynamic: bool = False, 3751 new_line: bool = False, 3752 ) -> str: 3753 expressions = expression.args.get(key or "expressions") if expression else sqls 3754 3755 if not expressions: 3756 return "" 3757 3758 if flat: 3759 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3760 3761 num_sqls = len(expressions) 3762 result_sqls = [] 3763 3764 for i, e in enumerate(expressions): 3765 sql = self.sql(e, comment=False) 3766 if not sql: 3767 continue 3768 3769 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3770 3771 if self.pretty: 3772 if self.leading_comma: 3773 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3774 else: 3775 result_sqls.append( 3776 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3777 ) 3778 else: 3779 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3780 3781 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3782 if new_line: 3783 result_sqls.insert(0, "") 3784 result_sqls.append("") 3785 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3786 else: 3787 result_sql = "".join(result_sqls) 3788 3789 return ( 3790 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3791 if indent 3792 else result_sql 3793 ) 3794 3795 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3796 flat = flat or isinstance(expression.parent, exp.Properties) 3797 expressions_sql = self.expressions(expression, flat=flat) 3798 if flat: 3799 return f"{op} {expressions_sql}" 3800 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3801 3802 def naked_property(self, expression: exp.Property) -> str: 3803 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3804 if not property_name: 3805 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3806 return f"{property_name} {self.sql(expression, 'this')}" 3807 3808 def tag_sql(self, expression: exp.Tag) -> str: 3809 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3810 3811 def token_sql(self, token_type: TokenType) -> str: 3812 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3813 3814 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3815 this = self.sql(expression, "this") 3816 expressions = self.no_identify(self.expressions, expression) 3817 expressions = ( 3818 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3819 ) 3820 return f"{this}{expressions}" if expressions.strip() != "" else this 3821 3822 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3823 this = self.sql(expression, "this") 3824 expressions = self.expressions(expression, flat=True) 3825 return f"{this}({expressions})" 3826 3827 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3828 return self.binary(expression, "=>") 3829 3830 def when_sql(self, expression: exp.When) -> str: 3831 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3832 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3833 condition = self.sql(expression, "condition") 3834 condition = f" AND {condition}" if condition else "" 3835 3836 then_expression = expression.args.get("then") 3837 if isinstance(then_expression, exp.Insert): 3838 this = self.sql(then_expression, "this") 3839 this = f"INSERT {this}" if this else "INSERT" 3840 then = self.sql(then_expression, "expression") 3841 then = f"{this} VALUES {then}" if then else this 3842 elif isinstance(then_expression, exp.Update): 3843 if isinstance(then_expression.args.get("expressions"), exp.Star): 3844 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3845 else: 3846 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3847 else: 3848 then = self.sql(then_expression) 3849 return f"WHEN {matched}{source}{condition} THEN {then}" 3850 3851 def whens_sql(self, expression: exp.Whens) -> str: 3852 return self.expressions(expression, sep=" ", indent=False) 3853 3854 def merge_sql(self, expression: exp.Merge) -> str: 3855 table = expression.this 3856 table_alias = "" 3857 3858 hints = table.args.get("hints") 3859 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3860 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3861 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3862 3863 this = self.sql(table) 3864 using = f"USING {self.sql(expression, 'using')}" 3865 on = f"ON {self.sql(expression, 'on')}" 3866 whens = self.sql(expression, "whens") 3867 3868 returning = self.sql(expression, "returning") 3869 if returning: 3870 whens = f"{whens}{returning}" 3871 3872 sep = self.sep() 3873 3874 return self.prepend_ctes( 3875 expression, 3876 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3877 ) 3878 3879 @unsupported_args("format") 3880 def tochar_sql(self, expression: exp.ToChar) -> str: 3881 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3882 3883 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3884 if not self.SUPPORTS_TO_NUMBER: 3885 self.unsupported("Unsupported TO_NUMBER function") 3886 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3887 3888 fmt = expression.args.get("format") 3889 if not fmt: 3890 self.unsupported("Conversion format is required for TO_NUMBER") 3891 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3892 3893 return self.func("TO_NUMBER", expression.this, fmt) 3894 3895 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3896 this = self.sql(expression, "this") 3897 kind = self.sql(expression, "kind") 3898 settings_sql = self.expressions(expression, key="settings", sep=" ") 3899 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3900 return f"{this}({kind}{args})" 3901 3902 def dictrange_sql(self, expression: exp.DictRange) -> str: 3903 this = self.sql(expression, "this") 3904 max = self.sql(expression, "max") 3905 min = self.sql(expression, "min") 3906 return f"{this}(MIN {min} MAX {max})" 3907 3908 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3909 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3910 3911 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3912 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3913 3914 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3915 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3916 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3917 3918 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3919 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3920 expressions = self.expressions(expression, flat=True) 3921 expressions = f" {self.wrap(expressions)}" if expressions else "" 3922 buckets = self.sql(expression, "buckets") 3923 kind = self.sql(expression, "kind") 3924 buckets = f" BUCKETS {buckets}" if buckets else "" 3925 order = self.sql(expression, "order") 3926 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3927 3928 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3929 return "" 3930 3931 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3932 expressions = self.expressions(expression, key="expressions", flat=True) 3933 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3934 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3935 buckets = self.sql(expression, "buckets") 3936 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3937 3938 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3939 this = self.sql(expression, "this") 3940 having = self.sql(expression, "having") 3941 3942 if having: 3943 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3944 3945 return self.func("ANY_VALUE", this) 3946 3947 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3948 transform = self.func("TRANSFORM", *expression.expressions) 3949 row_format_before = self.sql(expression, "row_format_before") 3950 row_format_before = f" {row_format_before}" if row_format_before else "" 3951 record_writer = self.sql(expression, "record_writer") 3952 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3953 using = f" USING {self.sql(expression, 'command_script')}" 3954 schema = self.sql(expression, "schema") 3955 schema = f" AS {schema}" if schema else "" 3956 row_format_after = self.sql(expression, "row_format_after") 3957 row_format_after = f" {row_format_after}" if row_format_after else "" 3958 record_reader = self.sql(expression, "record_reader") 3959 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3960 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3961 3962 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3963 key_block_size = self.sql(expression, "key_block_size") 3964 if key_block_size: 3965 return f"KEY_BLOCK_SIZE = {key_block_size}" 3966 3967 using = self.sql(expression, "using") 3968 if using: 3969 return f"USING {using}" 3970 3971 parser = self.sql(expression, "parser") 3972 if parser: 3973 return f"WITH PARSER {parser}" 3974 3975 comment = self.sql(expression, "comment") 3976 if comment: 3977 return f"COMMENT {comment}" 3978 3979 visible = expression.args.get("visible") 3980 if visible is not None: 3981 return "VISIBLE" if visible else "INVISIBLE" 3982 3983 engine_attr = self.sql(expression, "engine_attr") 3984 if engine_attr: 3985 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3986 3987 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3988 if secondary_engine_attr: 3989 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3990 3991 self.unsupported("Unsupported index constraint option.") 3992 return "" 3993 3994 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3995 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3996 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3997 3998 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 3999 kind = self.sql(expression, "kind") 4000 kind = f"{kind} INDEX" if kind else "INDEX" 4001 this = self.sql(expression, "this") 4002 this = f" {this}" if this else "" 4003 index_type = self.sql(expression, "index_type") 4004 index_type = f" USING {index_type}" if index_type else "" 4005 expressions = self.expressions(expression, flat=True) 4006 expressions = f" ({expressions})" if expressions else "" 4007 options = self.expressions(expression, key="options", sep=" ") 4008 options = f" {options}" if options else "" 4009 return f"{kind}{this}{index_type}{expressions}{options}" 4010 4011 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4012 if self.NVL2_SUPPORTED: 4013 return self.function_fallback_sql(expression) 4014 4015 case = exp.Case().when( 4016 expression.this.is_(exp.null()).not_(copy=False), 4017 expression.args["true"], 4018 copy=False, 4019 ) 4020 else_cond = expression.args.get("false") 4021 if else_cond: 4022 case.else_(else_cond, copy=False) 4023 4024 return self.sql(case) 4025 4026 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4027 this = self.sql(expression, "this") 4028 expr = self.sql(expression, "expression") 4029 iterator = self.sql(expression, "iterator") 4030 condition = self.sql(expression, "condition") 4031 condition = f" IF {condition}" if condition else "" 4032 return f"{this} FOR {expr} IN {iterator}{condition}" 4033 4034 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4035 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4036 4037 def opclass_sql(self, expression: exp.Opclass) -> str: 4038 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4039 4040 def predict_sql(self, expression: exp.Predict) -> str: 4041 model = self.sql(expression, "this") 4042 model = f"MODEL {model}" 4043 table = self.sql(expression, "expression") 4044 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4045 parameters = self.sql(expression, "params_struct") 4046 return self.func("PREDICT", model, table, parameters or None) 4047 4048 def forin_sql(self, expression: exp.ForIn) -> str: 4049 this = self.sql(expression, "this") 4050 expression_sql = self.sql(expression, "expression") 4051 return f"FOR {this} DO {expression_sql}" 4052 4053 def refresh_sql(self, expression: exp.Refresh) -> str: 4054 this = self.sql(expression, "this") 4055 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4056 return f"REFRESH {table}{this}" 4057 4058 def toarray_sql(self, expression: exp.ToArray) -> str: 4059 arg = expression.this 4060 if not arg.type: 4061 from sqlglot.optimizer.annotate_types import annotate_types 4062 4063 arg = annotate_types(arg, dialect=self.dialect) 4064 4065 if arg.is_type(exp.DataType.Type.ARRAY): 4066 return self.sql(arg) 4067 4068 cond_for_null = arg.is_(exp.null()) 4069 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4070 4071 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4072 this = expression.this 4073 time_format = self.format_time(expression) 4074 4075 if time_format: 4076 return self.sql( 4077 exp.cast( 4078 exp.StrToTime(this=this, format=expression.args["format"]), 4079 exp.DataType.Type.TIME, 4080 ) 4081 ) 4082 4083 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4084 return self.sql(this) 4085 4086 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4087 4088 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4089 this = expression.this 4090 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4091 return self.sql(this) 4092 4093 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4094 4095 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4096 this = expression.this 4097 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4098 return self.sql(this) 4099 4100 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4101 4102 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4103 this = expression.this 4104 time_format = self.format_time(expression) 4105 4106 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4107 return self.sql( 4108 exp.cast( 4109 exp.StrToTime(this=this, format=expression.args["format"]), 4110 exp.DataType.Type.DATE, 4111 ) 4112 ) 4113 4114 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4115 return self.sql(this) 4116 4117 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4118 4119 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4120 return self.sql( 4121 exp.func( 4122 "DATEDIFF", 4123 expression.this, 4124 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4125 "day", 4126 ) 4127 ) 4128 4129 def lastday_sql(self, expression: exp.LastDay) -> str: 4130 if self.LAST_DAY_SUPPORTS_DATE_PART: 4131 return self.function_fallback_sql(expression) 4132 4133 unit = expression.text("unit") 4134 if unit and unit != "MONTH": 4135 self.unsupported("Date parts are not supported in LAST_DAY.") 4136 4137 return self.func("LAST_DAY", expression.this) 4138 4139 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4140 from sqlglot.dialects.dialect import unit_to_str 4141 4142 return self.func( 4143 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4144 ) 4145 4146 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4147 if self.CAN_IMPLEMENT_ARRAY_ANY: 4148 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4149 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4150 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4151 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4152 4153 from sqlglot.dialects import Dialect 4154 4155 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4156 if self.dialect.__class__ != Dialect: 4157 self.unsupported("ARRAY_ANY is unsupported") 4158 4159 return self.function_fallback_sql(expression) 4160 4161 def struct_sql(self, expression: exp.Struct) -> str: 4162 expression.set( 4163 "expressions", 4164 [ 4165 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4166 if isinstance(e, exp.PropertyEQ) 4167 else e 4168 for e in expression.expressions 4169 ], 4170 ) 4171 4172 return self.function_fallback_sql(expression) 4173 4174 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4175 low = self.sql(expression, "this") 4176 high = self.sql(expression, "expression") 4177 4178 return f"{low} TO {high}" 4179 4180 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4181 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4182 tables = f" {self.expressions(expression)}" 4183 4184 exists = " IF EXISTS" if expression.args.get("exists") else "" 4185 4186 on_cluster = self.sql(expression, "cluster") 4187 on_cluster = f" {on_cluster}" if on_cluster else "" 4188 4189 identity = self.sql(expression, "identity") 4190 identity = f" {identity} IDENTITY" if identity else "" 4191 4192 option = self.sql(expression, "option") 4193 option = f" {option}" if option else "" 4194 4195 partition = self.sql(expression, "partition") 4196 partition = f" {partition}" if partition else "" 4197 4198 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4199 4200 # This transpiles T-SQL's CONVERT function 4201 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4202 def convert_sql(self, expression: exp.Convert) -> str: 4203 to = expression.this 4204 value = expression.expression 4205 style = expression.args.get("style") 4206 safe = expression.args.get("safe") 4207 strict = expression.args.get("strict") 4208 4209 if not to or not value: 4210 return "" 4211 4212 # Retrieve length of datatype and override to default if not specified 4213 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4214 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4215 4216 transformed: t.Optional[exp.Expression] = None 4217 cast = exp.Cast if strict else exp.TryCast 4218 4219 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4220 if isinstance(style, exp.Literal) and style.is_int: 4221 from sqlglot.dialects.tsql import TSQL 4222 4223 style_value = style.name 4224 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4225 if not converted_style: 4226 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4227 4228 fmt = exp.Literal.string(converted_style) 4229 4230 if to.this == exp.DataType.Type.DATE: 4231 transformed = exp.StrToDate(this=value, format=fmt) 4232 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4233 transformed = exp.StrToTime(this=value, format=fmt) 4234 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4235 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4236 elif to.this == exp.DataType.Type.TEXT: 4237 transformed = exp.TimeToStr(this=value, format=fmt) 4238 4239 if not transformed: 4240 transformed = cast(this=value, to=to, safe=safe) 4241 4242 return self.sql(transformed) 4243 4244 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4245 this = expression.this 4246 if isinstance(this, exp.JSONPathWildcard): 4247 this = self.json_path_part(this) 4248 return f".{this}" if this else "" 4249 4250 if exp.SAFE_IDENTIFIER_RE.match(this): 4251 return f".{this}" 4252 4253 this = self.json_path_part(this) 4254 return ( 4255 f"[{this}]" 4256 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4257 else f".{this}" 4258 ) 4259 4260 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4261 this = self.json_path_part(expression.this) 4262 return f"[{this}]" if this else "" 4263 4264 def _simplify_unless_literal(self, expression: E) -> E: 4265 if not isinstance(expression, exp.Literal): 4266 from sqlglot.optimizer.simplify import simplify 4267 4268 expression = simplify(expression, dialect=self.dialect) 4269 4270 return expression 4271 4272 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4273 this = expression.this 4274 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4275 self.unsupported( 4276 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4277 ) 4278 return self.sql(this) 4279 4280 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4281 # The first modifier here will be the one closest to the AggFunc's arg 4282 mods = sorted( 4283 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4284 key=lambda x: 0 4285 if isinstance(x, exp.HavingMax) 4286 else (1 if isinstance(x, exp.Order) else 2), 4287 ) 4288 4289 if mods: 4290 mod = mods[0] 4291 this = expression.__class__(this=mod.this.copy()) 4292 this.meta["inline"] = True 4293 mod.this.replace(this) 4294 return self.sql(expression.this) 4295 4296 agg_func = expression.find(exp.AggFunc) 4297 4298 if agg_func: 4299 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4300 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4301 4302 return f"{self.sql(expression, 'this')} {text}" 4303 4304 def _replace_line_breaks(self, string: str) -> str: 4305 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4306 if self.pretty: 4307 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4308 return string 4309 4310 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4311 option = self.sql(expression, "this") 4312 4313 if expression.expressions: 4314 upper = option.upper() 4315 4316 # Snowflake FILE_FORMAT options are separated by whitespace 4317 sep = " " if upper == "FILE_FORMAT" else ", " 4318 4319 # Databricks copy/format options do not set their list of values with EQ 4320 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4321 values = self.expressions(expression, flat=True, sep=sep) 4322 return f"{option}{op}({values})" 4323 4324 value = self.sql(expression, "expression") 4325 4326 if not value: 4327 return option 4328 4329 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4330 4331 return f"{option}{op}{value}" 4332 4333 def credentials_sql(self, expression: exp.Credentials) -> str: 4334 cred_expr = expression.args.get("credentials") 4335 if isinstance(cred_expr, exp.Literal): 4336 # Redshift case: CREDENTIALS <string> 4337 credentials = self.sql(expression, "credentials") 4338 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4339 else: 4340 # Snowflake case: CREDENTIALS = (...) 4341 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4342 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4343 4344 storage = self.sql(expression, "storage") 4345 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4346 4347 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4348 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4349 4350 iam_role = self.sql(expression, "iam_role") 4351 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4352 4353 region = self.sql(expression, "region") 4354 region = f" REGION {region}" if region else "" 4355 4356 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4357 4358 def copy_sql(self, expression: exp.Copy) -> str: 4359 this = self.sql(expression, "this") 4360 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4361 4362 credentials = self.sql(expression, "credentials") 4363 credentials = self.seg(credentials) if credentials else "" 4364 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4365 files = self.expressions(expression, key="files", flat=True) 4366 4367 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4368 params = self.expressions( 4369 expression, 4370 key="params", 4371 sep=sep, 4372 new_line=True, 4373 skip_last=True, 4374 skip_first=True, 4375 indent=self.COPY_PARAMS_ARE_WRAPPED, 4376 ) 4377 4378 if params: 4379 if self.COPY_PARAMS_ARE_WRAPPED: 4380 params = f" WITH ({params})" 4381 elif not self.pretty: 4382 params = f" {params}" 4383 4384 return f"COPY{this}{kind} {files}{credentials}{params}" 4385 4386 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4387 return "" 4388 4389 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4390 on_sql = "ON" if expression.args.get("on") else "OFF" 4391 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4392 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4393 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4394 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4395 4396 if filter_col or retention_period: 4397 on_sql = self.func("ON", filter_col, retention_period) 4398 4399 return f"DATA_DELETION={on_sql}" 4400 4401 def maskingpolicycolumnconstraint_sql( 4402 self, expression: exp.MaskingPolicyColumnConstraint 4403 ) -> str: 4404 this = self.sql(expression, "this") 4405 expressions = self.expressions(expression, flat=True) 4406 expressions = f" USING ({expressions})" if expressions else "" 4407 return f"MASKING POLICY {this}{expressions}" 4408 4409 def gapfill_sql(self, expression: exp.GapFill) -> str: 4410 this = self.sql(expression, "this") 4411 this = f"TABLE {this}" 4412 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4413 4414 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4415 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4416 4417 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4418 this = self.sql(expression, "this") 4419 expr = expression.expression 4420 4421 if isinstance(expr, exp.Func): 4422 # T-SQL's CLR functions are case sensitive 4423 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4424 else: 4425 expr = self.sql(expression, "expression") 4426 4427 return self.scope_resolution(expr, this) 4428 4429 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4430 if self.PARSE_JSON_NAME is None: 4431 return self.sql(expression.this) 4432 4433 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4434 4435 def rand_sql(self, expression: exp.Rand) -> str: 4436 lower = self.sql(expression, "lower") 4437 upper = self.sql(expression, "upper") 4438 4439 if lower and upper: 4440 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4441 return self.func("RAND", expression.this) 4442 4443 def changes_sql(self, expression: exp.Changes) -> str: 4444 information = self.sql(expression, "information") 4445 information = f"INFORMATION => {information}" 4446 at_before = self.sql(expression, "at_before") 4447 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4448 end = self.sql(expression, "end") 4449 end = f"{self.seg('')}{end}" if end else "" 4450 4451 return f"CHANGES ({information}){at_before}{end}" 4452 4453 def pad_sql(self, expression: exp.Pad) -> str: 4454 prefix = "L" if expression.args.get("is_left") else "R" 4455 4456 fill_pattern = self.sql(expression, "fill_pattern") or None 4457 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4458 fill_pattern = "' '" 4459 4460 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4461 4462 def summarize_sql(self, expression: exp.Summarize) -> str: 4463 table = " TABLE" if expression.args.get("table") else "" 4464 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4465 4466 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4467 generate_series = exp.GenerateSeries(**expression.args) 4468 4469 parent = expression.parent 4470 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4471 parent = parent.parent 4472 4473 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4474 return self.sql(exp.Unnest(expressions=[generate_series])) 4475 4476 if isinstance(parent, exp.Select): 4477 self.unsupported("GenerateSeries projection unnesting is not supported.") 4478 4479 return self.sql(generate_series) 4480 4481 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4482 exprs = expression.expressions 4483 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4484 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4485 else: 4486 rhs = self.expressions(expression) 4487 4488 return self.func(name, expression.this, rhs or None) 4489 4490 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4491 if self.SUPPORTS_CONVERT_TIMEZONE: 4492 return self.function_fallback_sql(expression) 4493 4494 source_tz = expression.args.get("source_tz") 4495 target_tz = expression.args.get("target_tz") 4496 timestamp = expression.args.get("timestamp") 4497 4498 if source_tz and timestamp: 4499 timestamp = exp.AtTimeZone( 4500 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4501 ) 4502 4503 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4504 4505 return self.sql(expr) 4506 4507 def json_sql(self, expression: exp.JSON) -> str: 4508 this = self.sql(expression, "this") 4509 this = f" {this}" if this else "" 4510 4511 _with = expression.args.get("with") 4512 4513 if _with is None: 4514 with_sql = "" 4515 elif not _with: 4516 with_sql = " WITHOUT" 4517 else: 4518 with_sql = " WITH" 4519 4520 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4521 4522 return f"JSON{this}{with_sql}{unique_sql}" 4523 4524 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4525 def _generate_on_options(arg: t.Any) -> str: 4526 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4527 4528 path = self.sql(expression, "path") 4529 returning = self.sql(expression, "returning") 4530 returning = f" RETURNING {returning}" if returning else "" 4531 4532 on_condition = self.sql(expression, "on_condition") 4533 on_condition = f" {on_condition}" if on_condition else "" 4534 4535 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4536 4537 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4538 else_ = "ELSE " if expression.args.get("else_") else "" 4539 condition = self.sql(expression, "expression") 4540 condition = f"WHEN {condition} THEN " if condition else else_ 4541 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4542 return f"{condition}{insert}" 4543 4544 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4545 kind = self.sql(expression, "kind") 4546 expressions = self.seg(self.expressions(expression, sep=" ")) 4547 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4548 return res 4549 4550 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4551 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4552 empty = expression.args.get("empty") 4553 empty = ( 4554 f"DEFAULT {empty} ON EMPTY" 4555 if isinstance(empty, exp.Expression) 4556 else self.sql(expression, "empty") 4557 ) 4558 4559 error = expression.args.get("error") 4560 error = ( 4561 f"DEFAULT {error} ON ERROR" 4562 if isinstance(error, exp.Expression) 4563 else self.sql(expression, "error") 4564 ) 4565 4566 if error and empty: 4567 error = ( 4568 f"{empty} {error}" 4569 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4570 else f"{error} {empty}" 4571 ) 4572 empty = "" 4573 4574 null = self.sql(expression, "null") 4575 4576 return f"{empty}{error}{null}" 4577 4578 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4579 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4580 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4581 4582 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4583 this = self.sql(expression, "this") 4584 path = self.sql(expression, "path") 4585 4586 passing = self.expressions(expression, "passing") 4587 passing = f" PASSING {passing}" if passing else "" 4588 4589 on_condition = self.sql(expression, "on_condition") 4590 on_condition = f" {on_condition}" if on_condition else "" 4591 4592 path = f"{path}{passing}{on_condition}" 4593 4594 return self.func("JSON_EXISTS", this, path) 4595 4596 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4597 array_agg = self.function_fallback_sql(expression) 4598 4599 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4600 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4601 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4602 parent = expression.parent 4603 if isinstance(parent, exp.Filter): 4604 parent_cond = parent.expression.this 4605 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4606 else: 4607 this = expression.this 4608 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4609 if this.find(exp.Column): 4610 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4611 this_sql = ( 4612 self.expressions(this) 4613 if isinstance(this, exp.Distinct) 4614 else self.sql(expression, "this") 4615 ) 4616 4617 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4618 4619 return array_agg 4620 4621 def apply_sql(self, expression: exp.Apply) -> str: 4622 this = self.sql(expression, "this") 4623 expr = self.sql(expression, "expression") 4624 4625 return f"{this} APPLY({expr})" 4626 4627 def grant_sql(self, expression: exp.Grant) -> str: 4628 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4629 4630 kind = self.sql(expression, "kind") 4631 kind = f" {kind}" if kind else "" 4632 4633 securable = self.sql(expression, "securable") 4634 securable = f" {securable}" if securable else "" 4635 4636 principals = self.expressions(expression, key="principals", flat=True) 4637 4638 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4639 4640 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4641 4642 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4643 this = self.sql(expression, "this") 4644 columns = self.expressions(expression, flat=True) 4645 columns = f"({columns})" if columns else "" 4646 4647 return f"{this}{columns}" 4648 4649 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4650 this = self.sql(expression, "this") 4651 4652 kind = self.sql(expression, "kind") 4653 kind = f"{kind} " if kind else "" 4654 4655 return f"{kind}{this}" 4656 4657 def columns_sql(self, expression: exp.Columns): 4658 func = self.function_fallback_sql(expression) 4659 if expression.args.get("unpack"): 4660 func = f"*{func}" 4661 4662 return func 4663 4664 def overlay_sql(self, expression: exp.Overlay): 4665 this = self.sql(expression, "this") 4666 expr = self.sql(expression, "expression") 4667 from_sql = self.sql(expression, "from") 4668 for_sql = self.sql(expression, "for") 4669 for_sql = f" FOR {for_sql}" if for_sql else "" 4670 4671 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4672 4673 @unsupported_args("format") 4674 def todouble_sql(self, expression: exp.ToDouble) -> str: 4675 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4676 4677 def string_sql(self, expression: exp.String) -> str: 4678 this = expression.this 4679 zone = expression.args.get("zone") 4680 4681 if zone: 4682 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4683 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4684 # set for source_tz to transpile the time conversion before the STRING cast 4685 this = exp.ConvertTimezone( 4686 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4687 ) 4688 4689 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4690 4691 def median_sql(self, expression: exp.Median): 4692 if not self.SUPPORTS_MEDIAN: 4693 return self.sql( 4694 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4695 ) 4696 4697 return self.function_fallback_sql(expression) 4698 4699 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4700 filler = self.sql(expression, "this") 4701 filler = f" {filler}" if filler else "" 4702 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4703 return f"TRUNCATE{filler} {with_count}" 4704 4705 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4706 if self.SUPPORTS_UNIX_SECONDS: 4707 return self.function_fallback_sql(expression) 4708 4709 start_ts = exp.cast( 4710 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4711 ) 4712 4713 return self.sql( 4714 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4715 ) 4716 4717 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4718 dim = expression.expression 4719 4720 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4721 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4722 if not (dim.is_int and dim.name == "1"): 4723 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4724 dim = None 4725 4726 # If dimension is required but not specified, default initialize it 4727 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4728 dim = exp.Literal.number(1) 4729 4730 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4731 4732 def attach_sql(self, expression: exp.Attach) -> str: 4733 this = self.sql(expression, "this") 4734 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4735 expressions = self.expressions(expression) 4736 expressions = f" ({expressions})" if expressions else "" 4737 4738 return f"ATTACH{exists_sql} {this}{expressions}" 4739 4740 def detach_sql(self, expression: exp.Detach) -> str: 4741 this = self.sql(expression, "this") 4742 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 4743 4744 return f"DETACH{exists_sql} {this}" 4745 4746 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4747 this = self.sql(expression, "this") 4748 value = self.sql(expression, "expression") 4749 value = f" {value}" if value else "" 4750 return f"{this}{value}" 4751 4752 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4753 this_sql = self.sql(expression, "this") 4754 if isinstance(expression.this, exp.Table): 4755 this_sql = f"TABLE {this_sql}" 4756 4757 return self.func( 4758 "FEATURES_AT_TIME", 4759 this_sql, 4760 expression.args.get("time"), 4761 expression.args.get("num_rows"), 4762 expression.args.get("ignore_feature_nulls"), 4763 ) 4764 4765 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4766 return ( 4767 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4768 ) 4769 4770 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4771 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4772 encode = f"{encode} {self.sql(expression, 'this')}" 4773 4774 properties = expression.args.get("properties") 4775 if properties: 4776 encode = f"{encode} {self.properties(properties)}" 4777 4778 return encode 4779 4780 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4781 this = self.sql(expression, "this") 4782 include = f"INCLUDE {this}" 4783 4784 column_def = self.sql(expression, "column_def") 4785 if column_def: 4786 include = f"{include} {column_def}" 4787 4788 alias = self.sql(expression, "alias") 4789 if alias: 4790 include = f"{include} AS {alias}" 4791 4792 return include 4793 4794 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4795 name = f"NAME {self.sql(expression, 'this')}" 4796 return self.func("XMLELEMENT", name, *expression.expressions) 4797 4798 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4799 partitions = self.expressions(expression, "partition_expressions") 4800 create = self.expressions(expression, "create_expressions") 4801 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4802 4803 def partitionbyrangepropertydynamic_sql( 4804 self, expression: exp.PartitionByRangePropertyDynamic 4805 ) -> str: 4806 start = self.sql(expression, "start") 4807 end = self.sql(expression, "end") 4808 4809 every = expression.args["every"] 4810 if isinstance(every, exp.Interval) and every.this.is_string: 4811 every.this.replace(exp.Literal.number(every.name)) 4812 4813 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4814 4815 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4816 name = self.sql(expression, "this") 4817 values = self.expressions(expression, flat=True) 4818 4819 return f"NAME {name} VALUE {values}" 4820 4821 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4822 kind = self.sql(expression, "kind") 4823 sample = self.sql(expression, "sample") 4824 return f"SAMPLE {sample} {kind}" 4825 4826 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4827 kind = self.sql(expression, "kind") 4828 option = self.sql(expression, "option") 4829 option = f" {option}" if option else "" 4830 this = self.sql(expression, "this") 4831 this = f" {this}" if this else "" 4832 columns = self.expressions(expression) 4833 columns = f" {columns}" if columns else "" 4834 return f"{kind}{option} STATISTICS{this}{columns}" 4835 4836 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4837 this = self.sql(expression, "this") 4838 columns = self.expressions(expression) 4839 inner_expression = self.sql(expression, "expression") 4840 inner_expression = f" {inner_expression}" if inner_expression else "" 4841 update_options = self.sql(expression, "update_options") 4842 update_options = f" {update_options} UPDATE" if update_options else "" 4843 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4844 4845 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4846 kind = self.sql(expression, "kind") 4847 kind = f" {kind}" if kind else "" 4848 return f"DELETE{kind} STATISTICS" 4849 4850 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4851 inner_expression = self.sql(expression, "expression") 4852 return f"LIST CHAINED ROWS{inner_expression}" 4853 4854 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4855 kind = self.sql(expression, "kind") 4856 this = self.sql(expression, "this") 4857 this = f" {this}" if this else "" 4858 inner_expression = self.sql(expression, "expression") 4859 return f"VALIDATE {kind}{this}{inner_expression}" 4860 4861 def analyze_sql(self, expression: exp.Analyze) -> str: 4862 options = self.expressions(expression, key="options", sep=" ") 4863 options = f" {options}" if options else "" 4864 kind = self.sql(expression, "kind") 4865 kind = f" {kind}" if kind else "" 4866 this = self.sql(expression, "this") 4867 this = f" {this}" if this else "" 4868 mode = self.sql(expression, "mode") 4869 mode = f" {mode}" if mode else "" 4870 properties = self.sql(expression, "properties") 4871 properties = f" {properties}" if properties else "" 4872 partition = self.sql(expression, "partition") 4873 partition = f" {partition}" if partition else "" 4874 inner_expression = self.sql(expression, "expression") 4875 inner_expression = f" {inner_expression}" if inner_expression else "" 4876 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4877 4878 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4879 this = self.sql(expression, "this") 4880 namespaces = self.expressions(expression, key="namespaces") 4881 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4882 passing = self.expressions(expression, key="passing") 4883 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4884 columns = self.expressions(expression, key="columns") 4885 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4886 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4887 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4888 4889 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4890 this = self.sql(expression, "this") 4891 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4892 4893 def export_sql(self, expression: exp.Export) -> str: 4894 this = self.sql(expression, "this") 4895 connection = self.sql(expression, "connection") 4896 connection = f"WITH CONNECTION {connection} " if connection else "" 4897 options = self.sql(expression, "options") 4898 return f"EXPORT DATA {connection}{options} AS {this}" 4899 4900 def declare_sql(self, expression: exp.Declare) -> str: 4901 return f"DECLARE {self.expressions(expression, flat=True)}" 4902 4903 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4904 variable = self.sql(expression, "this") 4905 default = self.sql(expression, "default") 4906 default = f" = {default}" if default else "" 4907 4908 kind = self.sql(expression, "kind") 4909 if isinstance(expression.args.get("kind"), exp.Schema): 4910 kind = f"TABLE {kind}" 4911 4912 return f"{variable} AS {kind}{default}" 4913 4914 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4915 kind = self.sql(expression, "kind") 4916 this = self.sql(expression, "this") 4917 set = self.sql(expression, "expression") 4918 using = self.sql(expression, "using") 4919 using = f" USING {using}" if using else "" 4920 4921 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4922 4923 return f"{kind_sql} {this} SET {set}{using}" 4924 4925 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 4926 params = self.expressions(expression, key="params", flat=True) 4927 return self.func(expression.name, *expression.expressions) + f"({params})" 4928 4929 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 4930 return self.func(expression.name, *expression.expressions) 4931 4932 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 4933 return self.anonymousaggfunc_sql(expression) 4934 4935 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 4936 return self.parameterizedagg_sql(expression) 4937 4938 def show_sql(self, expression: exp.Show) -> str: 4939 self.unsupported("Unsupported SHOW statement") 4940 return "" 4941 4942 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 4943 # Snowflake GET/PUT statements: 4944 # PUT <file> <internalStage> <properties> 4945 # GET <internalStage> <file> <properties> 4946 props = expression.args.get("properties") 4947 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 4948 this = self.sql(expression, "this") 4949 target = self.sql(expression, "target") 4950 4951 if isinstance(expression, exp.Put): 4952 return f"PUT {this} {target}{props_sql}" 4953 else: 4954 return f"GET {target} {this}{props_sql}" 4955 4956 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 4957 this = self.sql(expression, "this") 4958 expr = self.sql(expression, "expression") 4959 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 4960 return f"TRANSLATE({this} USING {expr}{with_error})"
logger =
<Logger sqlglot (WARNING)>
ESCAPED_UNICODE_RE =
re.compile('\\\\(\\d+)')
UNSUPPORTED_TEMPLATE =
"Argument '{}' is not supported for expression '{}' when targeting {}."
def
unsupported_args( *args: Union[str, Tuple[str, str]]) -> Callable[[Callable[[~G, ~E], str]], Callable[[~G, ~E], str]]:
30def unsupported_args( 31 *args: t.Union[str, t.Tuple[str, str]], 32) -> t.Callable[[GeneratorMethod], GeneratorMethod]: 33 """ 34 Decorator that can be used to mark certain args of an `Expression` subclass as unsupported. 35 It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg). 36 """ 37 diagnostic_by_arg: t.Dict[str, t.Optional[str]] = {} 38 for arg in args: 39 if isinstance(arg, str): 40 diagnostic_by_arg[arg] = None 41 else: 42 diagnostic_by_arg[arg[0]] = arg[1] 43 44 def decorator(func: GeneratorMethod) -> GeneratorMethod: 45 @wraps(func) 46 def _func(generator: G, expression: E) -> str: 47 expression_name = expression.__class__.__name__ 48 dialect_name = generator.dialect.__class__.__name__ 49 50 for arg_name, diagnostic in diagnostic_by_arg.items(): 51 if expression.args.get(arg_name): 52 diagnostic = diagnostic or UNSUPPORTED_TEMPLATE.format( 53 arg_name, expression_name, dialect_name 54 ) 55 generator.unsupported(diagnostic) 56 57 return func(generator, expression) 58 59 return _func 60 61 return decorator
Decorator that can be used to mark certain args of an Expression subclass as unsupported.
It expects a sequence of argument names or pairs of the form (argument_name, diagnostic_msg).
class
Generator:
75class Generator(metaclass=_Generator): 76 """ 77 Generator converts a given syntax tree to the corresponding SQL string. 78 79 Args: 80 pretty: Whether to format the produced SQL string. 81 Default: False. 82 identify: Determines when an identifier should be quoted. Possible values are: 83 False (default): Never quote, except in cases where it's mandatory by the dialect. 84 True or 'always': Always quote. 85 'safe': Only quote identifiers that are case insensitive. 86 normalize: Whether to normalize identifiers to lowercase. 87 Default: False. 88 pad: The pad size in a formatted string. For example, this affects the indentation of 89 a projection in a query, relative to its nesting level. 90 Default: 2. 91 indent: The indentation size in a formatted string. For example, this affects the 92 indentation of subqueries and filters under a `WHERE` clause. 93 Default: 2. 94 normalize_functions: How to normalize function names. Possible values are: 95 "upper" or True (default): Convert names to uppercase. 96 "lower": Convert names to lowercase. 97 False: Disables function name normalization. 98 unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. 99 Default ErrorLevel.WARN. 100 max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. 101 This is only relevant if unsupported_level is ErrorLevel.RAISE. 102 Default: 3 103 leading_comma: Whether the comma is leading or trailing in select expressions. 104 This is only relevant when generating in pretty mode. 105 Default: False 106 max_text_width: The max number of characters in a segment before creating new lines in pretty mode. 107 The default is on the smaller end because the length only represents a segment and not the true 108 line length. 109 Default: 80 110 comments: Whether to preserve comments in the output SQL code. 111 Default: True 112 """ 113 114 TRANSFORMS: t.Dict[t.Type[exp.Expression], t.Callable[..., str]] = { 115 **JSON_PATH_PART_TRANSFORMS, 116 exp.AllowedValuesProperty: lambda self, 117 e: f"ALLOWED_VALUES {self.expressions(e, flat=True)}", 118 exp.AnalyzeColumns: lambda self, e: self.sql(e, "this"), 119 exp.AnalyzeWith: lambda self, e: self.expressions(e, prefix="WITH ", sep=" "), 120 exp.ArrayContainsAll: lambda self, e: self.binary(e, "@>"), 121 exp.ArrayOverlaps: lambda self, e: self.binary(e, "&&"), 122 exp.AutoRefreshProperty: lambda self, e: f"AUTO REFRESH {self.sql(e, 'this')}", 123 exp.BackupProperty: lambda self, e: f"BACKUP {self.sql(e, 'this')}", 124 exp.CaseSpecificColumnConstraint: lambda _, 125 e: f"{'NOT ' if e.args.get('not_') else ''}CASESPECIFIC", 126 exp.Ceil: lambda self, e: self.ceil_floor(e), 127 exp.CharacterSetColumnConstraint: lambda self, e: f"CHARACTER SET {self.sql(e, 'this')}", 128 exp.CharacterSetProperty: lambda self, 129 e: f"{'DEFAULT ' if e.args.get('default') else ''}CHARACTER SET={self.sql(e, 'this')}", 130 exp.ClusteredColumnConstraint: lambda self, 131 e: f"CLUSTERED ({self.expressions(e, 'this', indent=False)})", 132 exp.CollateColumnConstraint: lambda self, e: f"COLLATE {self.sql(e, 'this')}", 133 exp.CommentColumnConstraint: lambda self, e: f"COMMENT {self.sql(e, 'this')}", 134 exp.ConnectByRoot: lambda self, e: f"CONNECT_BY_ROOT {self.sql(e, 'this')}", 135 exp.ConvertToCharset: lambda self, e: self.func( 136 "CONVERT", e.this, e.args["dest"], e.args.get("source") 137 ), 138 exp.CopyGrantsProperty: lambda *_: "COPY GRANTS", 139 exp.CredentialsProperty: lambda self, 140 e: f"CREDENTIALS=({self.expressions(e, 'expressions', sep=' ')})", 141 exp.DateFormatColumnConstraint: lambda self, e: f"FORMAT {self.sql(e, 'this')}", 142 exp.DefaultColumnConstraint: lambda self, e: f"DEFAULT {self.sql(e, 'this')}", 143 exp.DynamicProperty: lambda *_: "DYNAMIC", 144 exp.EmptyProperty: lambda *_: "EMPTY", 145 exp.EncodeColumnConstraint: lambda self, e: f"ENCODE {self.sql(e, 'this')}", 146 exp.EnviromentProperty: lambda self, e: f"ENVIRONMENT ({self.expressions(e, flat=True)})", 147 exp.EphemeralColumnConstraint: lambda self, 148 e: f"EPHEMERAL{(' ' + self.sql(e, 'this')) if e.this else ''}", 149 exp.ExcludeColumnConstraint: lambda self, e: f"EXCLUDE {self.sql(e, 'this').lstrip()}", 150 exp.ExecuteAsProperty: lambda self, e: self.naked_property(e), 151 exp.Except: lambda self, e: self.set_operations(e), 152 exp.ExternalProperty: lambda *_: "EXTERNAL", 153 exp.Floor: lambda self, e: self.ceil_floor(e), 154 exp.Get: lambda self, e: self.get_put_sql(e), 155 exp.GlobalProperty: lambda *_: "GLOBAL", 156 exp.HeapProperty: lambda *_: "HEAP", 157 exp.IcebergProperty: lambda *_: "ICEBERG", 158 exp.InheritsProperty: lambda self, e: f"INHERITS ({self.expressions(e, flat=True)})", 159 exp.InlineLengthColumnConstraint: lambda self, e: f"INLINE LENGTH {self.sql(e, 'this')}", 160 exp.InputModelProperty: lambda self, e: f"INPUT{self.sql(e, 'this')}", 161 exp.Intersect: lambda self, e: self.set_operations(e), 162 exp.IntervalSpan: lambda self, e: f"{self.sql(e, 'this')} TO {self.sql(e, 'expression')}", 163 exp.Int64: lambda self, e: self.sql(exp.cast(e.this, exp.DataType.Type.BIGINT)), 164 exp.LanguageProperty: lambda self, e: self.naked_property(e), 165 exp.LocationProperty: lambda self, e: self.naked_property(e), 166 exp.LogProperty: lambda _, e: f"{'NO ' if e.args.get('no') else ''}LOG", 167 exp.MaterializedProperty: lambda *_: "MATERIALIZED", 168 exp.NonClusteredColumnConstraint: lambda self, 169 e: f"NONCLUSTERED ({self.expressions(e, 'this', indent=False)})", 170 exp.NoPrimaryIndexProperty: lambda *_: "NO PRIMARY INDEX", 171 exp.NotForReplicationColumnConstraint: lambda *_: "NOT FOR REPLICATION", 172 exp.OnCommitProperty: lambda _, 173 e: f"ON COMMIT {'DELETE' if e.args.get('delete') else 'PRESERVE'} ROWS", 174 exp.OnProperty: lambda self, e: f"ON {self.sql(e, 'this')}", 175 exp.OnUpdateColumnConstraint: lambda self, e: f"ON UPDATE {self.sql(e, 'this')}", 176 exp.Operator: lambda self, e: self.binary(e, ""), # The operator is produced in `binary` 177 exp.OutputModelProperty: lambda self, e: f"OUTPUT{self.sql(e, 'this')}", 178 exp.PathColumnConstraint: lambda self, e: f"PATH {self.sql(e, 'this')}", 179 exp.PartitionedByBucket: lambda self, e: self.func("BUCKET", e.this, e.expression), 180 exp.PartitionByTruncate: lambda self, e: self.func("TRUNCATE", e.this, e.expression), 181 exp.PivotAny: lambda self, e: f"ANY{self.sql(e, 'this')}", 182 exp.ProjectionPolicyColumnConstraint: lambda self, 183 e: f"PROJECTION POLICY {self.sql(e, 'this')}", 184 exp.Put: lambda self, e: self.get_put_sql(e), 185 exp.RemoteWithConnectionModelProperty: lambda self, 186 e: f"REMOTE WITH CONNECTION {self.sql(e, 'this')}", 187 exp.ReturnsProperty: lambda self, e: ( 188 "RETURNS NULL ON NULL INPUT" if e.args.get("null") else self.naked_property(e) 189 ), 190 exp.SampleProperty: lambda self, e: f"SAMPLE BY {self.sql(e, 'this')}", 191 exp.SecureProperty: lambda *_: "SECURE", 192 exp.SecurityProperty: lambda self, e: f"SECURITY {self.sql(e, 'this')}", 193 exp.SetConfigProperty: lambda self, e: self.sql(e, "this"), 194 exp.SetProperty: lambda _, e: f"{'MULTI' if e.args.get('multi') else ''}SET", 195 exp.SettingsProperty: lambda self, e: f"SETTINGS{self.seg('')}{(self.expressions(e))}", 196 exp.SharingProperty: lambda self, e: f"SHARING={self.sql(e, 'this')}", 197 exp.SqlReadWriteProperty: lambda _, e: e.name, 198 exp.SqlSecurityProperty: lambda _, 199 e: f"SQL SECURITY {'DEFINER' if e.args.get('definer') else 'INVOKER'}", 200 exp.StabilityProperty: lambda _, e: e.name, 201 exp.Stream: lambda self, e: f"STREAM {self.sql(e, 'this')}", 202 exp.StreamingTableProperty: lambda *_: "STREAMING", 203 exp.StrictProperty: lambda *_: "STRICT", 204 exp.SwapTable: lambda self, e: f"SWAP WITH {self.sql(e, 'this')}", 205 exp.Tags: lambda self, e: f"TAG ({self.expressions(e, flat=True)})", 206 exp.TemporaryProperty: lambda *_: "TEMPORARY", 207 exp.TitleColumnConstraint: lambda self, e: f"TITLE {self.sql(e, 'this')}", 208 exp.ToMap: lambda self, e: f"MAP {self.sql(e, 'this')}", 209 exp.ToTableProperty: lambda self, e: f"TO {self.sql(e.this)}", 210 exp.TransformModelProperty: lambda self, e: self.func("TRANSFORM", *e.expressions), 211 exp.TransientProperty: lambda *_: "TRANSIENT", 212 exp.Union: lambda self, e: self.set_operations(e), 213 exp.UnloggedProperty: lambda *_: "UNLOGGED", 214 exp.UsingTemplateProperty: lambda self, e: f"USING TEMPLATE {self.sql(e, 'this')}", 215 exp.UsingData: lambda self, e: f"USING DATA {self.sql(e, 'this')}", 216 exp.Uuid: lambda *_: "UUID()", 217 exp.UppercaseColumnConstraint: lambda *_: "UPPERCASE", 218 exp.VarMap: lambda self, e: self.func("MAP", e.args["keys"], e.args["values"]), 219 exp.ViewAttributeProperty: lambda self, e: f"WITH {self.sql(e, 'this')}", 220 exp.VolatileProperty: lambda *_: "VOLATILE", 221 exp.WithJournalTableProperty: lambda self, e: f"WITH JOURNAL TABLE={self.sql(e, 'this')}", 222 exp.WithProcedureOptions: lambda self, e: f"WITH {self.expressions(e, flat=True)}", 223 exp.WithSchemaBindingProperty: lambda self, e: f"WITH SCHEMA {self.sql(e, 'this')}", 224 exp.WithOperator: lambda self, e: f"{self.sql(e, 'this')} WITH {self.sql(e, 'op')}", 225 exp.ForceProperty: lambda *_: "FORCE", 226 } 227 228 # Whether null ordering is supported in order by 229 # True: Full Support, None: No support, False: No support for certain cases 230 # such as window specifications, aggregate functions etc 231 NULL_ORDERING_SUPPORTED: t.Optional[bool] = True 232 233 # Whether ignore nulls is inside the agg or outside. 234 # FIRST(x IGNORE NULLS) OVER vs FIRST (x) IGNORE NULLS OVER 235 IGNORE_NULLS_IN_FUNC = False 236 237 # Whether locking reads (i.e. SELECT ... FOR UPDATE/SHARE) are supported 238 LOCKING_READS_SUPPORTED = False 239 240 # Whether the EXCEPT and INTERSECT operations can return duplicates 241 EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE = True 242 243 # Wrap derived values in parens, usually standard but spark doesn't support it 244 WRAP_DERIVED_VALUES = True 245 246 # Whether create function uses an AS before the RETURN 247 CREATE_FUNCTION_RETURN_AS = True 248 249 # Whether MERGE ... WHEN MATCHED BY SOURCE is allowed 250 MATCHED_BY_SOURCE = True 251 252 # Whether the INTERVAL expression works only with values like '1 day' 253 SINGLE_STRING_INTERVAL = False 254 255 # Whether the plural form of date parts like day (i.e. "days") is supported in INTERVALs 256 INTERVAL_ALLOWS_PLURAL_FORM = True 257 258 # Whether limit and fetch are supported (possible values: "ALL", "LIMIT", "FETCH") 259 LIMIT_FETCH = "ALL" 260 261 # Whether limit and fetch allows expresions or just limits 262 LIMIT_ONLY_LITERALS = False 263 264 # Whether a table is allowed to be renamed with a db 265 RENAME_TABLE_WITH_DB = True 266 267 # The separator for grouping sets and rollups 268 GROUPINGS_SEP = "," 269 270 # The string used for creating an index on a table 271 INDEX_ON = "ON" 272 273 # Whether join hints should be generated 274 JOIN_HINTS = True 275 276 # Whether table hints should be generated 277 TABLE_HINTS = True 278 279 # Whether query hints should be generated 280 QUERY_HINTS = True 281 282 # What kind of separator to use for query hints 283 QUERY_HINT_SEP = ", " 284 285 # Whether comparing against booleans (e.g. x IS TRUE) is supported 286 IS_BOOL_ALLOWED = True 287 288 # Whether to include the "SET" keyword in the "INSERT ... ON DUPLICATE KEY UPDATE" statement 289 DUPLICATE_KEY_UPDATE_WITH_SET = True 290 291 # Whether to generate the limit as TOP <value> instead of LIMIT <value> 292 LIMIT_IS_TOP = False 293 294 # Whether to generate INSERT INTO ... RETURNING or INSERT INTO RETURNING ... 295 RETURNING_END = True 296 297 # Whether to generate an unquoted value for EXTRACT's date part argument 298 EXTRACT_ALLOWS_QUOTES = True 299 300 # Whether TIMETZ / TIMESTAMPTZ will be generated using the "WITH TIME ZONE" syntax 301 TZ_TO_WITH_TIME_ZONE = False 302 303 # Whether the NVL2 function is supported 304 NVL2_SUPPORTED = True 305 306 # https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax 307 SELECT_KINDS: t.Tuple[str, ...] = ("STRUCT", "VALUE") 308 309 # Whether VALUES statements can be used as derived tables. 310 # MySQL 5 and Redshift do not allow this, so when False, it will convert 311 # SELECT * VALUES into SELECT UNION 312 VALUES_AS_TABLE = True 313 314 # Whether the word COLUMN is included when adding a column with ALTER TABLE 315 ALTER_TABLE_INCLUDE_COLUMN_KEYWORD = True 316 317 # UNNEST WITH ORDINALITY (presto) instead of UNNEST WITH OFFSET (bigquery) 318 UNNEST_WITH_ORDINALITY = True 319 320 # Whether FILTER (WHERE cond) can be used for conditional aggregation 321 AGGREGATE_FILTER_SUPPORTED = True 322 323 # Whether JOIN sides (LEFT, RIGHT) are supported in conjunction with SEMI/ANTI join kinds 324 SEMI_ANTI_JOIN_WITH_SIDE = True 325 326 # Whether to include the type of a computed column in the CREATE DDL 327 COMPUTED_COLUMN_WITH_TYPE = True 328 329 # Whether CREATE TABLE .. COPY .. is supported. False means we'll generate CLONE instead of COPY 330 SUPPORTS_TABLE_COPY = True 331 332 # Whether parentheses are required around the table sample's expression 333 TABLESAMPLE_REQUIRES_PARENS = True 334 335 # Whether a table sample clause's size needs to be followed by the ROWS keyword 336 TABLESAMPLE_SIZE_IS_ROWS = True 337 338 # The keyword(s) to use when generating a sample clause 339 TABLESAMPLE_KEYWORDS = "TABLESAMPLE" 340 341 # Whether the TABLESAMPLE clause supports a method name, like BERNOULLI 342 TABLESAMPLE_WITH_METHOD = True 343 344 # The keyword to use when specifying the seed of a sample clause 345 TABLESAMPLE_SEED_KEYWORD = "SEED" 346 347 # Whether COLLATE is a function instead of a binary operator 348 COLLATE_IS_FUNC = False 349 350 # Whether data types support additional specifiers like e.g. CHAR or BYTE (oracle) 351 DATA_TYPE_SPECIFIERS_ALLOWED = False 352 353 # Whether conditions require booleans WHERE x = 0 vs WHERE x 354 ENSURE_BOOLS = False 355 356 # Whether the "RECURSIVE" keyword is required when defining recursive CTEs 357 CTE_RECURSIVE_KEYWORD_REQUIRED = True 358 359 # Whether CONCAT requires >1 arguments 360 SUPPORTS_SINGLE_ARG_CONCAT = True 361 362 # Whether LAST_DAY function supports a date part argument 363 LAST_DAY_SUPPORTS_DATE_PART = True 364 365 # Whether named columns are allowed in table aliases 366 SUPPORTS_TABLE_ALIAS_COLUMNS = True 367 368 # Whether UNPIVOT aliases are Identifiers (False means they're Literals) 369 UNPIVOT_ALIASES_ARE_IDENTIFIERS = True 370 371 # What delimiter to use for separating JSON key/value pairs 372 JSON_KEY_VALUE_PAIR_SEP = ":" 373 374 # INSERT OVERWRITE TABLE x override 375 INSERT_OVERWRITE = " OVERWRITE TABLE" 376 377 # Whether the SELECT .. INTO syntax is used instead of CTAS 378 SUPPORTS_SELECT_INTO = False 379 380 # Whether UNLOGGED tables can be created 381 SUPPORTS_UNLOGGED_TABLES = False 382 383 # Whether the CREATE TABLE LIKE statement is supported 384 SUPPORTS_CREATE_TABLE_LIKE = True 385 386 # Whether the LikeProperty needs to be specified inside of the schema clause 387 LIKE_PROPERTY_INSIDE_SCHEMA = False 388 389 # Whether DISTINCT can be followed by multiple args in an AggFunc. If not, it will be 390 # transpiled into a series of CASE-WHEN-ELSE, ultimately using a tuple conseisting of the args 391 MULTI_ARG_DISTINCT = True 392 393 # Whether the JSON extraction operators expect a value of type JSON 394 JSON_TYPE_REQUIRED_FOR_EXTRACTION = False 395 396 # Whether bracketed keys like ["foo"] are supported in JSON paths 397 JSON_PATH_BRACKETED_KEY_SUPPORTED = True 398 399 # Whether to escape keys using single quotes in JSON paths 400 JSON_PATH_SINGLE_QUOTE_ESCAPE = False 401 402 # The JSONPathPart expressions supported by this dialect 403 SUPPORTED_JSON_PATH_PARTS = ALL_JSON_PATH_PARTS.copy() 404 405 # Whether any(f(x) for x in array) can be implemented by this dialect 406 CAN_IMPLEMENT_ARRAY_ANY = False 407 408 # Whether the function TO_NUMBER is supported 409 SUPPORTS_TO_NUMBER = True 410 411 # Whether EXCLUDE in window specification is supported 412 SUPPORTS_WINDOW_EXCLUDE = False 413 414 # Whether or not set op modifiers apply to the outer set op or select. 415 # SELECT * FROM x UNION SELECT * FROM y LIMIT 1 416 # True means limit 1 happens after the set op, False means it it happens on y. 417 SET_OP_MODIFIERS = True 418 419 # Whether parameters from COPY statement are wrapped in parentheses 420 COPY_PARAMS_ARE_WRAPPED = True 421 422 # Whether values of params are set with "=" token or empty space 423 COPY_PARAMS_EQ_REQUIRED = False 424 425 # Whether COPY statement has INTO keyword 426 COPY_HAS_INTO_KEYWORD = True 427 428 # Whether the conditional TRY(expression) function is supported 429 TRY_SUPPORTED = True 430 431 # Whether the UESCAPE syntax in unicode strings is supported 432 SUPPORTS_UESCAPE = True 433 434 # The keyword to use when generating a star projection with excluded columns 435 STAR_EXCEPT = "EXCEPT" 436 437 # The HEX function name 438 HEX_FUNC = "HEX" 439 440 # The keywords to use when prefixing & separating WITH based properties 441 WITH_PROPERTIES_PREFIX = "WITH" 442 443 # Whether to quote the generated expression of exp.JsonPath 444 QUOTE_JSON_PATH = True 445 446 # Whether the text pattern/fill (3rd) parameter of RPAD()/LPAD() is optional (defaults to space) 447 PAD_FILL_PATTERN_IS_REQUIRED = False 448 449 # Whether a projection can explode into multiple rows, e.g. by unnesting an array. 450 SUPPORTS_EXPLODING_PROJECTIONS = True 451 452 # Whether ARRAY_CONCAT can be generated with varlen args or if it should be reduced to 2-arg version 453 ARRAY_CONCAT_IS_VAR_LEN = True 454 455 # Whether CONVERT_TIMEZONE() is supported; if not, it will be generated as exp.AtTimeZone 456 SUPPORTS_CONVERT_TIMEZONE = False 457 458 # Whether MEDIAN(expr) is supported; if not, it will be generated as PERCENTILE_CONT(expr, 0.5) 459 SUPPORTS_MEDIAN = True 460 461 # Whether UNIX_SECONDS(timestamp) is supported 462 SUPPORTS_UNIX_SECONDS = False 463 464 # The name to generate for the JSONPath expression. If `None`, only `this` will be generated 465 PARSE_JSON_NAME: t.Optional[str] = "PARSE_JSON" 466 467 # The function name of the exp.ArraySize expression 468 ARRAY_SIZE_NAME: str = "ARRAY_LENGTH" 469 470 # The syntax to use when altering the type of a column 471 ALTER_SET_TYPE = "SET DATA TYPE" 472 473 # Whether exp.ArraySize should generate the dimension arg too (valid for Postgres & DuckDB) 474 # None -> Doesn't support it at all 475 # False (DuckDB) -> Has backwards-compatible support, but preferably generated without 476 # True (Postgres) -> Explicitly requires it 477 ARRAY_SIZE_DIM_REQUIRED: t.Optional[bool] = None 478 479 TYPE_MAPPING = { 480 exp.DataType.Type.DATETIME2: "TIMESTAMP", 481 exp.DataType.Type.NCHAR: "CHAR", 482 exp.DataType.Type.NVARCHAR: "VARCHAR", 483 exp.DataType.Type.MEDIUMTEXT: "TEXT", 484 exp.DataType.Type.LONGTEXT: "TEXT", 485 exp.DataType.Type.TINYTEXT: "TEXT", 486 exp.DataType.Type.BLOB: "VARBINARY", 487 exp.DataType.Type.MEDIUMBLOB: "BLOB", 488 exp.DataType.Type.LONGBLOB: "BLOB", 489 exp.DataType.Type.TINYBLOB: "BLOB", 490 exp.DataType.Type.INET: "INET", 491 exp.DataType.Type.ROWVERSION: "VARBINARY", 492 exp.DataType.Type.SMALLDATETIME: "TIMESTAMP", 493 } 494 495 TIME_PART_SINGULARS = { 496 "MICROSECONDS": "MICROSECOND", 497 "SECONDS": "SECOND", 498 "MINUTES": "MINUTE", 499 "HOURS": "HOUR", 500 "DAYS": "DAY", 501 "WEEKS": "WEEK", 502 "MONTHS": "MONTH", 503 "QUARTERS": "QUARTER", 504 "YEARS": "YEAR", 505 } 506 507 AFTER_HAVING_MODIFIER_TRANSFORMS = { 508 "cluster": lambda self, e: self.sql(e, "cluster"), 509 "distribute": lambda self, e: self.sql(e, "distribute"), 510 "sort": lambda self, e: self.sql(e, "sort"), 511 "windows": lambda self, e: ( 512 self.seg("WINDOW ") + self.expressions(e, key="windows", flat=True) 513 if e.args.get("windows") 514 else "" 515 ), 516 "qualify": lambda self, e: self.sql(e, "qualify"), 517 } 518 519 TOKEN_MAPPING: t.Dict[TokenType, str] = {} 520 521 STRUCT_DELIMITER = ("<", ">") 522 523 PARAMETER_TOKEN = "@" 524 NAMED_PLACEHOLDER_TOKEN = ":" 525 526 EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: t.Set[str] = set() 527 528 PROPERTIES_LOCATION = { 529 exp.AllowedValuesProperty: exp.Properties.Location.POST_SCHEMA, 530 exp.AlgorithmProperty: exp.Properties.Location.POST_CREATE, 531 exp.AutoIncrementProperty: exp.Properties.Location.POST_SCHEMA, 532 exp.AutoRefreshProperty: exp.Properties.Location.POST_SCHEMA, 533 exp.BackupProperty: exp.Properties.Location.POST_SCHEMA, 534 exp.BlockCompressionProperty: exp.Properties.Location.POST_NAME, 535 exp.CharacterSetProperty: exp.Properties.Location.POST_SCHEMA, 536 exp.ChecksumProperty: exp.Properties.Location.POST_NAME, 537 exp.CollateProperty: exp.Properties.Location.POST_SCHEMA, 538 exp.CopyGrantsProperty: exp.Properties.Location.POST_SCHEMA, 539 exp.Cluster: exp.Properties.Location.POST_SCHEMA, 540 exp.ClusteredByProperty: exp.Properties.Location.POST_SCHEMA, 541 exp.DistributedByProperty: exp.Properties.Location.POST_SCHEMA, 542 exp.DuplicateKeyProperty: exp.Properties.Location.POST_SCHEMA, 543 exp.DataBlocksizeProperty: exp.Properties.Location.POST_NAME, 544 exp.DataDeletionProperty: exp.Properties.Location.POST_SCHEMA, 545 exp.DefinerProperty: exp.Properties.Location.POST_CREATE, 546 exp.DictRange: exp.Properties.Location.POST_SCHEMA, 547 exp.DictProperty: exp.Properties.Location.POST_SCHEMA, 548 exp.DynamicProperty: exp.Properties.Location.POST_CREATE, 549 exp.DistKeyProperty: exp.Properties.Location.POST_SCHEMA, 550 exp.DistStyleProperty: exp.Properties.Location.POST_SCHEMA, 551 exp.EmptyProperty: exp.Properties.Location.POST_SCHEMA, 552 exp.EncodeProperty: exp.Properties.Location.POST_EXPRESSION, 553 exp.EngineProperty: exp.Properties.Location.POST_SCHEMA, 554 exp.EnviromentProperty: exp.Properties.Location.POST_SCHEMA, 555 exp.ExecuteAsProperty: exp.Properties.Location.POST_SCHEMA, 556 exp.ExternalProperty: exp.Properties.Location.POST_CREATE, 557 exp.FallbackProperty: exp.Properties.Location.POST_NAME, 558 exp.FileFormatProperty: exp.Properties.Location.POST_WITH, 559 exp.FreespaceProperty: exp.Properties.Location.POST_NAME, 560 exp.GlobalProperty: exp.Properties.Location.POST_CREATE, 561 exp.HeapProperty: exp.Properties.Location.POST_WITH, 562 exp.InheritsProperty: exp.Properties.Location.POST_SCHEMA, 563 exp.IcebergProperty: exp.Properties.Location.POST_CREATE, 564 exp.IncludeProperty: exp.Properties.Location.POST_SCHEMA, 565 exp.InputModelProperty: exp.Properties.Location.POST_SCHEMA, 566 exp.IsolatedLoadingProperty: exp.Properties.Location.POST_NAME, 567 exp.JournalProperty: exp.Properties.Location.POST_NAME, 568 exp.LanguageProperty: exp.Properties.Location.POST_SCHEMA, 569 exp.LikeProperty: exp.Properties.Location.POST_SCHEMA, 570 exp.LocationProperty: exp.Properties.Location.POST_SCHEMA, 571 exp.LockProperty: exp.Properties.Location.POST_SCHEMA, 572 exp.LockingProperty: exp.Properties.Location.POST_ALIAS, 573 exp.LogProperty: exp.Properties.Location.POST_NAME, 574 exp.MaterializedProperty: exp.Properties.Location.POST_CREATE, 575 exp.MergeBlockRatioProperty: exp.Properties.Location.POST_NAME, 576 exp.NoPrimaryIndexProperty: exp.Properties.Location.POST_EXPRESSION, 577 exp.OnProperty: exp.Properties.Location.POST_SCHEMA, 578 exp.OnCommitProperty: exp.Properties.Location.POST_EXPRESSION, 579 exp.Order: exp.Properties.Location.POST_SCHEMA, 580 exp.OutputModelProperty: exp.Properties.Location.POST_SCHEMA, 581 exp.PartitionedByProperty: exp.Properties.Location.POST_WITH, 582 exp.PartitionedOfProperty: exp.Properties.Location.POST_SCHEMA, 583 exp.PrimaryKey: exp.Properties.Location.POST_SCHEMA, 584 exp.Property: exp.Properties.Location.POST_WITH, 585 exp.RemoteWithConnectionModelProperty: exp.Properties.Location.POST_SCHEMA, 586 exp.ReturnsProperty: exp.Properties.Location.POST_SCHEMA, 587 exp.RowFormatProperty: exp.Properties.Location.POST_SCHEMA, 588 exp.RowFormatDelimitedProperty: exp.Properties.Location.POST_SCHEMA, 589 exp.RowFormatSerdeProperty: exp.Properties.Location.POST_SCHEMA, 590 exp.SampleProperty: exp.Properties.Location.POST_SCHEMA, 591 exp.SchemaCommentProperty: exp.Properties.Location.POST_SCHEMA, 592 exp.SecureProperty: exp.Properties.Location.POST_CREATE, 593 exp.SecurityProperty: exp.Properties.Location.POST_SCHEMA, 594 exp.SerdeProperties: exp.Properties.Location.POST_SCHEMA, 595 exp.Set: exp.Properties.Location.POST_SCHEMA, 596 exp.SettingsProperty: exp.Properties.Location.POST_SCHEMA, 597 exp.SetProperty: exp.Properties.Location.POST_CREATE, 598 exp.SetConfigProperty: exp.Properties.Location.POST_SCHEMA, 599 exp.SharingProperty: exp.Properties.Location.POST_EXPRESSION, 600 exp.SequenceProperties: exp.Properties.Location.POST_EXPRESSION, 601 exp.SortKeyProperty: exp.Properties.Location.POST_SCHEMA, 602 exp.SqlReadWriteProperty: exp.Properties.Location.POST_SCHEMA, 603 exp.SqlSecurityProperty: exp.Properties.Location.POST_CREATE, 604 exp.StabilityProperty: exp.Properties.Location.POST_SCHEMA, 605 exp.StorageHandlerProperty: exp.Properties.Location.POST_SCHEMA, 606 exp.StreamingTableProperty: exp.Properties.Location.POST_CREATE, 607 exp.StrictProperty: exp.Properties.Location.POST_SCHEMA, 608 exp.Tags: exp.Properties.Location.POST_WITH, 609 exp.TemporaryProperty: exp.Properties.Location.POST_CREATE, 610 exp.ToTableProperty: exp.Properties.Location.POST_SCHEMA, 611 exp.TransientProperty: exp.Properties.Location.POST_CREATE, 612 exp.TransformModelProperty: exp.Properties.Location.POST_SCHEMA, 613 exp.MergeTreeTTL: exp.Properties.Location.POST_SCHEMA, 614 exp.UnloggedProperty: exp.Properties.Location.POST_CREATE, 615 exp.UsingTemplateProperty: exp.Properties.Location.POST_SCHEMA, 616 exp.ViewAttributeProperty: exp.Properties.Location.POST_SCHEMA, 617 exp.VolatileProperty: exp.Properties.Location.POST_CREATE, 618 exp.WithDataProperty: exp.Properties.Location.POST_EXPRESSION, 619 exp.WithJournalTableProperty: exp.Properties.Location.POST_NAME, 620 exp.WithProcedureOptions: exp.Properties.Location.POST_SCHEMA, 621 exp.WithSchemaBindingProperty: exp.Properties.Location.POST_SCHEMA, 622 exp.WithSystemVersioningProperty: exp.Properties.Location.POST_SCHEMA, 623 exp.ForceProperty: exp.Properties.Location.POST_CREATE, 624 } 625 626 # Keywords that can't be used as unquoted identifier names 627 RESERVED_KEYWORDS: t.Set[str] = set() 628 629 # Expressions whose comments are separated from them for better formatting 630 WITH_SEPARATED_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 631 exp.Command, 632 exp.Create, 633 exp.Describe, 634 exp.Delete, 635 exp.Drop, 636 exp.From, 637 exp.Insert, 638 exp.Join, 639 exp.MultitableInserts, 640 exp.Select, 641 exp.SetOperation, 642 exp.Update, 643 exp.Where, 644 exp.With, 645 ) 646 647 # Expressions that should not have their comments generated in maybe_comment 648 EXCLUDE_COMMENTS: t.Tuple[t.Type[exp.Expression], ...] = ( 649 exp.Binary, 650 exp.SetOperation, 651 ) 652 653 # Expressions that can remain unwrapped when appearing in the context of an INTERVAL 654 UNWRAPPED_INTERVAL_VALUES: t.Tuple[t.Type[exp.Expression], ...] = ( 655 exp.Column, 656 exp.Literal, 657 exp.Neg, 658 exp.Paren, 659 ) 660 661 PARAMETERIZABLE_TEXT_TYPES = { 662 exp.DataType.Type.NVARCHAR, 663 exp.DataType.Type.VARCHAR, 664 exp.DataType.Type.CHAR, 665 exp.DataType.Type.NCHAR, 666 } 667 668 # Expressions that need to have all CTEs under them bubbled up to them 669 EXPRESSIONS_WITHOUT_NESTED_CTES: t.Set[t.Type[exp.Expression]] = set() 670 671 RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS: t.Tuple[t.Type[exp.Expression], ...] = () 672 673 SENTINEL_LINE_BREAK = "__SQLGLOT__LB__" 674 675 __slots__ = ( 676 "pretty", 677 "identify", 678 "normalize", 679 "pad", 680 "_indent", 681 "normalize_functions", 682 "unsupported_level", 683 "max_unsupported", 684 "leading_comma", 685 "max_text_width", 686 "comments", 687 "dialect", 688 "unsupported_messages", 689 "_escaped_quote_end", 690 "_escaped_identifier_end", 691 "_next_name", 692 "_identifier_start", 693 "_identifier_end", 694 "_quote_json_path_key_using_brackets", 695 ) 696 697 def __init__( 698 self, 699 pretty: t.Optional[bool] = None, 700 identify: str | bool = False, 701 normalize: bool = False, 702 pad: int = 2, 703 indent: int = 2, 704 normalize_functions: t.Optional[str | bool] = None, 705 unsupported_level: ErrorLevel = ErrorLevel.WARN, 706 max_unsupported: int = 3, 707 leading_comma: bool = False, 708 max_text_width: int = 80, 709 comments: bool = True, 710 dialect: DialectType = None, 711 ): 712 import sqlglot 713 from sqlglot.dialects import Dialect 714 715 self.pretty = pretty if pretty is not None else sqlglot.pretty 716 self.identify = identify 717 self.normalize = normalize 718 self.pad = pad 719 self._indent = indent 720 self.unsupported_level = unsupported_level 721 self.max_unsupported = max_unsupported 722 self.leading_comma = leading_comma 723 self.max_text_width = max_text_width 724 self.comments = comments 725 self.dialect = Dialect.get_or_raise(dialect) 726 727 # This is both a Dialect property and a Generator argument, so we prioritize the latter 728 self.normalize_functions = ( 729 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 730 ) 731 732 self.unsupported_messages: t.List[str] = [] 733 self._escaped_quote_end: str = ( 734 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 735 ) 736 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 737 738 self._next_name = name_sequence("_t") 739 740 self._identifier_start = self.dialect.IDENTIFIER_START 741 self._identifier_end = self.dialect.IDENTIFIER_END 742 743 self._quote_json_path_key_using_brackets = True 744 745 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 746 """ 747 Generates the SQL string corresponding to the given syntax tree. 748 749 Args: 750 expression: The syntax tree. 751 copy: Whether to copy the expression. The generator performs mutations so 752 it is safer to copy. 753 754 Returns: 755 The SQL string corresponding to `expression`. 756 """ 757 if copy: 758 expression = expression.copy() 759 760 expression = self.preprocess(expression) 761 762 self.unsupported_messages = [] 763 sql = self.sql(expression).strip() 764 765 if self.pretty: 766 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 767 768 if self.unsupported_level == ErrorLevel.IGNORE: 769 return sql 770 771 if self.unsupported_level == ErrorLevel.WARN: 772 for msg in self.unsupported_messages: 773 logger.warning(msg) 774 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 775 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 776 777 return sql 778 779 def preprocess(self, expression: exp.Expression) -> exp.Expression: 780 """Apply generic preprocessing transformations to a given expression.""" 781 expression = self._move_ctes_to_top_level(expression) 782 783 if self.ENSURE_BOOLS: 784 from sqlglot.transforms import ensure_bools 785 786 expression = ensure_bools(expression) 787 788 return expression 789 790 def _move_ctes_to_top_level(self, expression: E) -> E: 791 if ( 792 not expression.parent 793 and type(expression) in self.EXPRESSIONS_WITHOUT_NESTED_CTES 794 and any(node.parent is not expression for node in expression.find_all(exp.With)) 795 ): 796 from sqlglot.transforms import move_ctes_to_top_level 797 798 expression = move_ctes_to_top_level(expression) 799 return expression 800 801 def unsupported(self, message: str) -> None: 802 if self.unsupported_level == ErrorLevel.IMMEDIATE: 803 raise UnsupportedError(message) 804 self.unsupported_messages.append(message) 805 806 def sep(self, sep: str = " ") -> str: 807 return f"{sep.strip()}\n" if self.pretty else sep 808 809 def seg(self, sql: str, sep: str = " ") -> str: 810 return f"{self.sep(sep)}{sql}" 811 812 def pad_comment(self, comment: str) -> str: 813 comment = " " + comment if comment[0].strip() else comment 814 comment = comment + " " if comment[-1].strip() else comment 815 return comment 816 817 def maybe_comment( 818 self, 819 sql: str, 820 expression: t.Optional[exp.Expression] = None, 821 comments: t.Optional[t.List[str]] = None, 822 separated: bool = False, 823 ) -> str: 824 comments = ( 825 ((expression and expression.comments) if comments is None else comments) # type: ignore 826 if self.comments 827 else None 828 ) 829 830 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 831 return sql 832 833 comments_sql = " ".join( 834 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 835 ) 836 837 if not comments_sql: 838 return sql 839 840 comments_sql = self._replace_line_breaks(comments_sql) 841 842 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 843 return ( 844 f"{self.sep()}{comments_sql}{sql}" 845 if not sql or sql[0].isspace() 846 else f"{comments_sql}{self.sep()}{sql}" 847 ) 848 849 return f"{sql} {comments_sql}" 850 851 def wrap(self, expression: exp.Expression | str) -> str: 852 this_sql = ( 853 self.sql(expression) 854 if isinstance(expression, exp.UNWRAPPED_QUERIES) 855 else self.sql(expression, "this") 856 ) 857 if not this_sql: 858 return "()" 859 860 this_sql = self.indent(this_sql, level=1, pad=0) 861 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}" 862 863 def no_identify(self, func: t.Callable[..., str], *args, **kwargs) -> str: 864 original = self.identify 865 self.identify = False 866 result = func(*args, **kwargs) 867 self.identify = original 868 return result 869 870 def normalize_func(self, name: str) -> str: 871 if self.normalize_functions == "upper" or self.normalize_functions is True: 872 return name.upper() 873 if self.normalize_functions == "lower": 874 return name.lower() 875 return name 876 877 def indent( 878 self, 879 sql: str, 880 level: int = 0, 881 pad: t.Optional[int] = None, 882 skip_first: bool = False, 883 skip_last: bool = False, 884 ) -> str: 885 if not self.pretty or not sql: 886 return sql 887 888 pad = self.pad if pad is None else pad 889 lines = sql.split("\n") 890 891 return "\n".join( 892 ( 893 line 894 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 895 else f"{' ' * (level * self._indent + pad)}{line}" 896 ) 897 for i, line in enumerate(lines) 898 ) 899 900 def sql( 901 self, 902 expression: t.Optional[str | exp.Expression], 903 key: t.Optional[str] = None, 904 comment: bool = True, 905 ) -> str: 906 if not expression: 907 return "" 908 909 if isinstance(expression, str): 910 return expression 911 912 if key: 913 value = expression.args.get(key) 914 if value: 915 return self.sql(value) 916 return "" 917 918 transform = self.TRANSFORMS.get(expression.__class__) 919 920 if callable(transform): 921 sql = transform(self, expression) 922 elif isinstance(expression, exp.Expression): 923 exp_handler_name = f"{expression.key}_sql" 924 925 if hasattr(self, exp_handler_name): 926 sql = getattr(self, exp_handler_name)(expression) 927 elif isinstance(expression, exp.Func): 928 sql = self.function_fallback_sql(expression) 929 elif isinstance(expression, exp.Property): 930 sql = self.property_sql(expression) 931 else: 932 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 933 else: 934 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 935 936 return self.maybe_comment(sql, expression) if self.comments and comment else sql 937 938 def uncache_sql(self, expression: exp.Uncache) -> str: 939 table = self.sql(expression, "this") 940 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 941 return f"UNCACHE TABLE{exists_sql} {table}" 942 943 def cache_sql(self, expression: exp.Cache) -> str: 944 lazy = " LAZY" if expression.args.get("lazy") else "" 945 table = self.sql(expression, "this") 946 options = expression.args.get("options") 947 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 948 sql = self.sql(expression, "expression") 949 sql = f" AS{self.sep()}{sql}" if sql else "" 950 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 951 return self.prepend_ctes(expression, sql) 952 953 def characterset_sql(self, expression: exp.CharacterSet) -> str: 954 if isinstance(expression.parent, exp.Cast): 955 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 956 default = "DEFAULT " if expression.args.get("default") else "" 957 return f"{default}CHARACTER SET={self.sql(expression, 'this')}" 958 959 def column_parts(self, expression: exp.Column) -> str: 960 return ".".join( 961 self.sql(part) 962 for part in ( 963 expression.args.get("catalog"), 964 expression.args.get("db"), 965 expression.args.get("table"), 966 expression.args.get("this"), 967 ) 968 if part 969 ) 970 971 def column_sql(self, expression: exp.Column) -> str: 972 join_mark = " (+)" if expression.args.get("join_mark") else "" 973 974 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 975 join_mark = "" 976 self.unsupported("Outer join syntax using the (+) operator is not supported.") 977 978 return f"{self.column_parts(expression)}{join_mark}" 979 980 def columnposition_sql(self, expression: exp.ColumnPosition) -> str: 981 this = self.sql(expression, "this") 982 this = f" {this}" if this else "" 983 position = self.sql(expression, "position") 984 return f"{position}{this}" 985 986 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 987 column = self.sql(expression, "this") 988 kind = self.sql(expression, "kind") 989 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 990 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 991 kind = f"{sep}{kind}" if kind else "" 992 constraints = f" {constraints}" if constraints else "" 993 position = self.sql(expression, "position") 994 position = f" {position}" if position else "" 995 996 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 997 kind = "" 998 999 return f"{exists}{column}{kind}{constraints}{position}" 1000 1001 def columnconstraint_sql(self, expression: exp.ColumnConstraint) -> str: 1002 this = self.sql(expression, "this") 1003 kind_sql = self.sql(expression, "kind").strip() 1004 return f"CONSTRAINT {this} {kind_sql}" if this else kind_sql 1005 1006 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1007 this = self.sql(expression, "this") 1008 if expression.args.get("not_null"): 1009 persisted = " PERSISTED NOT NULL" 1010 elif expression.args.get("persisted"): 1011 persisted = " PERSISTED" 1012 else: 1013 persisted = "" 1014 return f"AS {this}{persisted}" 1015 1016 def autoincrementcolumnconstraint_sql(self, _) -> str: 1017 return self.token_sql(TokenType.AUTO_INCREMENT) 1018 1019 def compresscolumnconstraint_sql(self, expression: exp.CompressColumnConstraint) -> str: 1020 if isinstance(expression.this, list): 1021 this = self.wrap(self.expressions(expression, key="this", flat=True)) 1022 else: 1023 this = self.sql(expression, "this") 1024 1025 return f"COMPRESS {this}" 1026 1027 def generatedasidentitycolumnconstraint_sql( 1028 self, expression: exp.GeneratedAsIdentityColumnConstraint 1029 ) -> str: 1030 this = "" 1031 if expression.this is not None: 1032 on_null = " ON NULL" if expression.args.get("on_null") else "" 1033 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1034 1035 start = expression.args.get("start") 1036 start = f"START WITH {start}" if start else "" 1037 increment = expression.args.get("increment") 1038 increment = f" INCREMENT BY {increment}" if increment else "" 1039 minvalue = expression.args.get("minvalue") 1040 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1041 maxvalue = expression.args.get("maxvalue") 1042 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1043 cycle = expression.args.get("cycle") 1044 cycle_sql = "" 1045 1046 if cycle is not None: 1047 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1048 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1049 1050 sequence_opts = "" 1051 if start or increment or cycle_sql: 1052 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1053 sequence_opts = f" ({sequence_opts.strip()})" 1054 1055 expr = self.sql(expression, "expression") 1056 expr = f"({expr})" if expr else "IDENTITY" 1057 1058 return f"GENERATED{this} AS {expr}{sequence_opts}" 1059 1060 def generatedasrowcolumnconstraint_sql( 1061 self, expression: exp.GeneratedAsRowColumnConstraint 1062 ) -> str: 1063 start = "START" if expression.args.get("start") else "END" 1064 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1065 return f"GENERATED ALWAYS AS ROW {start}{hidden}" 1066 1067 def periodforsystemtimeconstraint_sql( 1068 self, expression: exp.PeriodForSystemTimeConstraint 1069 ) -> str: 1070 return f"PERIOD FOR SYSTEM_TIME ({self.sql(expression, 'this')}, {self.sql(expression, 'expression')})" 1071 1072 def notnullcolumnconstraint_sql(self, expression: exp.NotNullColumnConstraint) -> str: 1073 return f"{'' if expression.args.get('allow_null') else 'NOT '}NULL" 1074 1075 def transformcolumnconstraint_sql(self, expression: exp.TransformColumnConstraint) -> str: 1076 return f"AS {self.sql(expression, 'this')}" 1077 1078 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1079 desc = expression.args.get("desc") 1080 if desc is not None: 1081 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1082 options = self.expressions(expression, key="options", flat=True, sep=" ") 1083 options = f" {options}" if options else "" 1084 return f"PRIMARY KEY{options}" 1085 1086 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1087 this = self.sql(expression, "this") 1088 this = f" {this}" if this else "" 1089 index_type = expression.args.get("index_type") 1090 index_type = f" USING {index_type}" if index_type else "" 1091 on_conflict = self.sql(expression, "on_conflict") 1092 on_conflict = f" {on_conflict}" if on_conflict else "" 1093 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1094 options = self.expressions(expression, key="options", flat=True, sep=" ") 1095 options = f" {options}" if options else "" 1096 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}" 1097 1098 def createable_sql(self, expression: exp.Create, locations: t.DefaultDict) -> str: 1099 return self.sql(expression, "this") 1100 1101 def create_sql(self, expression: exp.Create) -> str: 1102 kind = self.sql(expression, "kind") 1103 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1104 properties = expression.args.get("properties") 1105 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1106 1107 this = self.createable_sql(expression, properties_locs) 1108 1109 properties_sql = "" 1110 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1111 exp.Properties.Location.POST_WITH 1112 ): 1113 properties_sql = self.sql( 1114 exp.Properties( 1115 expressions=[ 1116 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1117 *properties_locs[exp.Properties.Location.POST_WITH], 1118 ] 1119 ) 1120 ) 1121 1122 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1123 properties_sql = self.sep() + properties_sql 1124 elif not self.pretty: 1125 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1126 properties_sql = f" {properties_sql}" 1127 1128 begin = " BEGIN" if expression.args.get("begin") else "" 1129 end = " END" if expression.args.get("end") else "" 1130 1131 expression_sql = self.sql(expression, "expression") 1132 if expression_sql: 1133 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1134 1135 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1136 postalias_props_sql = "" 1137 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1138 postalias_props_sql = self.properties( 1139 exp.Properties( 1140 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1141 ), 1142 wrapped=False, 1143 ) 1144 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1145 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1146 1147 postindex_props_sql = "" 1148 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1149 postindex_props_sql = self.properties( 1150 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1151 wrapped=False, 1152 prefix=" ", 1153 ) 1154 1155 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1156 indexes = f" {indexes}" if indexes else "" 1157 index_sql = indexes + postindex_props_sql 1158 1159 replace = " OR REPLACE" if expression.args.get("replace") else "" 1160 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1161 unique = " UNIQUE" if expression.args.get("unique") else "" 1162 1163 clustered = expression.args.get("clustered") 1164 if clustered is None: 1165 clustered_sql = "" 1166 elif clustered: 1167 clustered_sql = " CLUSTERED COLUMNSTORE" 1168 else: 1169 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1170 1171 postcreate_props_sql = "" 1172 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1173 postcreate_props_sql = self.properties( 1174 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1175 sep=" ", 1176 prefix=" ", 1177 wrapped=False, 1178 ) 1179 1180 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1181 1182 postexpression_props_sql = "" 1183 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1184 postexpression_props_sql = self.properties( 1185 exp.Properties( 1186 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1187 ), 1188 sep=" ", 1189 prefix=" ", 1190 wrapped=False, 1191 ) 1192 1193 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1194 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1195 no_schema_binding = ( 1196 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1197 ) 1198 1199 clone = self.sql(expression, "clone") 1200 clone = f" {clone}" if clone else "" 1201 1202 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1203 properties_expression = f"{expression_sql}{properties_sql}" 1204 else: 1205 properties_expression = f"{properties_sql}{expression_sql}" 1206 1207 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1208 return self.prepend_ctes(expression, expression_sql) 1209 1210 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1211 start = self.sql(expression, "start") 1212 start = f"START WITH {start}" if start else "" 1213 increment = self.sql(expression, "increment") 1214 increment = f" INCREMENT BY {increment}" if increment else "" 1215 minvalue = self.sql(expression, "minvalue") 1216 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1217 maxvalue = self.sql(expression, "maxvalue") 1218 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1219 owned = self.sql(expression, "owned") 1220 owned = f" OWNED BY {owned}" if owned else "" 1221 1222 cache = expression.args.get("cache") 1223 if cache is None: 1224 cache_str = "" 1225 elif cache is True: 1226 cache_str = " CACHE" 1227 else: 1228 cache_str = f" CACHE {cache}" 1229 1230 options = self.expressions(expression, key="options", flat=True, sep=" ") 1231 options = f" {options}" if options else "" 1232 1233 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip() 1234 1235 def clone_sql(self, expression: exp.Clone) -> str: 1236 this = self.sql(expression, "this") 1237 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1238 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1239 return f"{shallow}{keyword} {this}" 1240 1241 def describe_sql(self, expression: exp.Describe) -> str: 1242 style = expression.args.get("style") 1243 style = f" {style}" if style else "" 1244 partition = self.sql(expression, "partition") 1245 partition = f" {partition}" if partition else "" 1246 format = self.sql(expression, "format") 1247 format = f" {format}" if format else "" 1248 1249 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}" 1250 1251 def heredoc_sql(self, expression: exp.Heredoc) -> str: 1252 tag = self.sql(expression, "tag") 1253 return f"${tag}${self.sql(expression, 'this')}${tag}$" 1254 1255 def prepend_ctes(self, expression: exp.Expression, sql: str) -> str: 1256 with_ = self.sql(expression, "with") 1257 if with_: 1258 sql = f"{with_}{self.sep()}{sql}" 1259 return sql 1260 1261 def with_sql(self, expression: exp.With) -> str: 1262 sql = self.expressions(expression, flat=True) 1263 recursive = ( 1264 "RECURSIVE " 1265 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1266 else "" 1267 ) 1268 search = self.sql(expression, "search") 1269 search = f" {search}" if search else "" 1270 1271 return f"WITH {recursive}{sql}{search}" 1272 1273 def cte_sql(self, expression: exp.CTE) -> str: 1274 alias = expression.args.get("alias") 1275 if alias: 1276 alias.add_comments(expression.pop_comments()) 1277 1278 alias_sql = self.sql(expression, "alias") 1279 1280 materialized = expression.args.get("materialized") 1281 if materialized is False: 1282 materialized = "NOT MATERIALIZED " 1283 elif materialized: 1284 materialized = "MATERIALIZED " 1285 1286 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}" 1287 1288 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1289 alias = self.sql(expression, "this") 1290 columns = self.expressions(expression, key="columns", flat=True) 1291 columns = f"({columns})" if columns else "" 1292 1293 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1294 columns = "" 1295 self.unsupported("Named columns are not supported in table alias.") 1296 1297 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1298 alias = self._next_name() 1299 1300 return f"{alias}{columns}" 1301 1302 def bitstring_sql(self, expression: exp.BitString) -> str: 1303 this = self.sql(expression, "this") 1304 if self.dialect.BIT_START: 1305 return f"{self.dialect.BIT_START}{this}{self.dialect.BIT_END}" 1306 return f"{int(this, 2)}" 1307 1308 def hexstring_sql( 1309 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1310 ) -> str: 1311 this = self.sql(expression, "this") 1312 is_integer_type = expression.args.get("is_integer") 1313 1314 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1315 not self.dialect.HEX_START and not binary_function_repr 1316 ): 1317 # Integer representation will be returned if: 1318 # - The read dialect treats the hex value as integer literal but not the write 1319 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1320 return f"{int(this, 16)}" 1321 1322 if not is_integer_type: 1323 # Read dialect treats the hex value as BINARY/BLOB 1324 if binary_function_repr: 1325 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1326 return self.func(binary_function_repr, exp.Literal.string(this)) 1327 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1328 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1329 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1330 1331 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}" 1332 1333 def bytestring_sql(self, expression: exp.ByteString) -> str: 1334 this = self.sql(expression, "this") 1335 if self.dialect.BYTE_START: 1336 return f"{self.dialect.BYTE_START}{this}{self.dialect.BYTE_END}" 1337 return this 1338 1339 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1340 this = self.sql(expression, "this") 1341 escape = expression.args.get("escape") 1342 1343 if self.dialect.UNICODE_START: 1344 escape_substitute = r"\\\1" 1345 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1346 else: 1347 escape_substitute = r"\\u\1" 1348 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1349 1350 if escape: 1351 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1352 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1353 else: 1354 escape_pattern = ESCAPED_UNICODE_RE 1355 escape_sql = "" 1356 1357 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1358 this = escape_pattern.sub(escape_substitute, this) 1359 1360 return f"{left_quote}{this}{right_quote}{escape_sql}" 1361 1362 def rawstring_sql(self, expression: exp.RawString) -> str: 1363 string = expression.this 1364 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1365 string = string.replace("\\", "\\\\") 1366 1367 string = self.escape_str(string, escape_backslash=False) 1368 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}" 1369 1370 def datatypeparam_sql(self, expression: exp.DataTypeParam) -> str: 1371 this = self.sql(expression, "this") 1372 specifier = self.sql(expression, "expression") 1373 specifier = f" {specifier}" if specifier and self.DATA_TYPE_SPECIFIERS_ALLOWED else "" 1374 return f"{this}{specifier}" 1375 1376 def datatype_sql(self, expression: exp.DataType) -> str: 1377 nested = "" 1378 values = "" 1379 interior = self.expressions(expression, flat=True) 1380 1381 type_value = expression.this 1382 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1383 type_sql = self.sql(expression, "kind") 1384 else: 1385 type_sql = ( 1386 self.TYPE_MAPPING.get(type_value, type_value.value) 1387 if isinstance(type_value, exp.DataType.Type) 1388 else type_value 1389 ) 1390 1391 if interior: 1392 if expression.args.get("nested"): 1393 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1394 if expression.args.get("values") is not None: 1395 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1396 values = self.expressions(expression, key="values", flat=True) 1397 values = f"{delimiters[0]}{values}{delimiters[1]}" 1398 elif type_value == exp.DataType.Type.INTERVAL: 1399 nested = f" {interior}" 1400 else: 1401 nested = f"({interior})" 1402 1403 type_sql = f"{type_sql}{nested}{values}" 1404 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1405 exp.DataType.Type.TIMETZ, 1406 exp.DataType.Type.TIMESTAMPTZ, 1407 ): 1408 type_sql = f"{type_sql} WITH TIME ZONE" 1409 1410 return type_sql 1411 1412 def directory_sql(self, expression: exp.Directory) -> str: 1413 local = "LOCAL " if expression.args.get("local") else "" 1414 row_format = self.sql(expression, "row_format") 1415 row_format = f" {row_format}" if row_format else "" 1416 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}" 1417 1418 def delete_sql(self, expression: exp.Delete) -> str: 1419 this = self.sql(expression, "this") 1420 this = f" FROM {this}" if this else "" 1421 using = self.sql(expression, "using") 1422 using = f" USING {using}" if using else "" 1423 cluster = self.sql(expression, "cluster") 1424 cluster = f" {cluster}" if cluster else "" 1425 where = self.sql(expression, "where") 1426 returning = self.sql(expression, "returning") 1427 limit = self.sql(expression, "limit") 1428 tables = self.expressions(expression, key="tables") 1429 tables = f" {tables}" if tables else "" 1430 if self.RETURNING_END: 1431 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1432 else: 1433 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1434 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}") 1435 1436 def drop_sql(self, expression: exp.Drop) -> str: 1437 this = self.sql(expression, "this") 1438 expressions = self.expressions(expression, flat=True) 1439 expressions = f" ({expressions})" if expressions else "" 1440 kind = expression.args["kind"] 1441 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1442 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1443 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1444 on_cluster = self.sql(expression, "cluster") 1445 on_cluster = f" {on_cluster}" if on_cluster else "" 1446 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1447 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1448 cascade = " CASCADE" if expression.args.get("cascade") else "" 1449 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1450 purge = " PURGE" if expression.args.get("purge") else "" 1451 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}" 1452 1453 def set_operation(self, expression: exp.SetOperation) -> str: 1454 op_type = type(expression) 1455 op_name = op_type.key.upper() 1456 1457 distinct = expression.args.get("distinct") 1458 if ( 1459 distinct is False 1460 and op_type in (exp.Except, exp.Intersect) 1461 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1462 ): 1463 self.unsupported(f"{op_name} ALL is not supported") 1464 1465 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1466 1467 if distinct is None: 1468 distinct = default_distinct 1469 if distinct is None: 1470 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1471 1472 if distinct is default_distinct: 1473 distinct_or_all = "" 1474 else: 1475 distinct_or_all = " DISTINCT" if distinct else " ALL" 1476 1477 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1478 side_kind = f"{side_kind} " if side_kind else "" 1479 1480 by_name = " BY NAME" if expression.args.get("by_name") else "" 1481 on = self.expressions(expression, key="on", flat=True) 1482 on = f" ON ({on})" if on else "" 1483 1484 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}" 1485 1486 def set_operations(self, expression: exp.SetOperation) -> str: 1487 if not self.SET_OP_MODIFIERS: 1488 limit = expression.args.get("limit") 1489 order = expression.args.get("order") 1490 1491 if limit or order: 1492 select = self._move_ctes_to_top_level( 1493 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1494 ) 1495 1496 if limit: 1497 select = select.limit(limit.pop(), copy=False) 1498 if order: 1499 select = select.order_by(order.pop(), copy=False) 1500 return self.sql(select) 1501 1502 sqls: t.List[str] = [] 1503 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1504 1505 while stack: 1506 node = stack.pop() 1507 1508 if isinstance(node, exp.SetOperation): 1509 stack.append(node.expression) 1510 stack.append( 1511 self.maybe_comment( 1512 self.set_operation(node), comments=node.comments, separated=True 1513 ) 1514 ) 1515 stack.append(node.this) 1516 else: 1517 sqls.append(self.sql(node)) 1518 1519 this = self.sep().join(sqls) 1520 this = self.query_modifiers(expression, this) 1521 return self.prepend_ctes(expression, this) 1522 1523 def fetch_sql(self, expression: exp.Fetch) -> str: 1524 direction = expression.args.get("direction") 1525 direction = f" {direction}" if direction else "" 1526 count = self.sql(expression, "count") 1527 count = f" {count}" if count else "" 1528 limit_options = self.sql(expression, "limit_options") 1529 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1530 return f"{self.seg('FETCH')}{direction}{count}{limit_options}" 1531 1532 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1533 percent = " PERCENT" if expression.args.get("percent") else "" 1534 rows = " ROWS" if expression.args.get("rows") else "" 1535 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1536 if not with_ties and rows: 1537 with_ties = " ONLY" 1538 return f"{percent}{rows}{with_ties}" 1539 1540 def filter_sql(self, expression: exp.Filter) -> str: 1541 if self.AGGREGATE_FILTER_SUPPORTED: 1542 this = self.sql(expression, "this") 1543 where = self.sql(expression, "expression").strip() 1544 return f"{this} FILTER({where})" 1545 1546 agg = expression.this 1547 agg_arg = agg.this 1548 cond = expression.expression.this 1549 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1550 return self.sql(agg) 1551 1552 def hint_sql(self, expression: exp.Hint) -> str: 1553 if not self.QUERY_HINTS: 1554 self.unsupported("Hints are not supported") 1555 return "" 1556 1557 return f" /*+ {self.expressions(expression, sep=self.QUERY_HINT_SEP).strip()} */" 1558 1559 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1560 using = self.sql(expression, "using") 1561 using = f" USING {using}" if using else "" 1562 columns = self.expressions(expression, key="columns", flat=True) 1563 columns = f"({columns})" if columns else "" 1564 partition_by = self.expressions(expression, key="partition_by", flat=True) 1565 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1566 where = self.sql(expression, "where") 1567 include = self.expressions(expression, key="include", flat=True) 1568 if include: 1569 include = f" INCLUDE ({include})" 1570 with_storage = self.expressions(expression, key="with_storage", flat=True) 1571 with_storage = f" WITH ({with_storage})" if with_storage else "" 1572 tablespace = self.sql(expression, "tablespace") 1573 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1574 on = self.sql(expression, "on") 1575 on = f" ON {on}" if on else "" 1576 1577 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}" 1578 1579 def index_sql(self, expression: exp.Index) -> str: 1580 unique = "UNIQUE " if expression.args.get("unique") else "" 1581 primary = "PRIMARY " if expression.args.get("primary") else "" 1582 amp = "AMP " if expression.args.get("amp") else "" 1583 name = self.sql(expression, "this") 1584 name = f"{name} " if name else "" 1585 table = self.sql(expression, "table") 1586 table = f"{self.INDEX_ON} {table}" if table else "" 1587 1588 index = "INDEX " if not table else "" 1589 1590 params = self.sql(expression, "params") 1591 return f"{unique}{primary}{amp}{index}{name}{table}{params}" 1592 1593 def identifier_sql(self, expression: exp.Identifier) -> str: 1594 text = expression.name 1595 lower = text.lower() 1596 text = lower if self.normalize and not expression.quoted else text 1597 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1598 if ( 1599 expression.quoted 1600 or self.dialect.can_identify(text, self.identify) 1601 or lower in self.RESERVED_KEYWORDS 1602 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1603 ): 1604 text = f"{self._identifier_start}{text}{self._identifier_end}" 1605 return text 1606 1607 def hex_sql(self, expression: exp.Hex) -> str: 1608 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1609 if self.dialect.HEX_LOWERCASE: 1610 text = self.func("LOWER", text) 1611 1612 return text 1613 1614 def lowerhex_sql(self, expression: exp.LowerHex) -> str: 1615 text = self.func(self.HEX_FUNC, self.sql(expression, "this")) 1616 if not self.dialect.HEX_LOWERCASE: 1617 text = self.func("LOWER", text) 1618 return text 1619 1620 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1621 input_format = self.sql(expression, "input_format") 1622 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1623 output_format = self.sql(expression, "output_format") 1624 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1625 return self.sep().join((input_format, output_format)) 1626 1627 def national_sql(self, expression: exp.National, prefix: str = "N") -> str: 1628 string = self.sql(exp.Literal.string(expression.name)) 1629 return f"{prefix}{string}" 1630 1631 def partition_sql(self, expression: exp.Partition) -> str: 1632 partition_keyword = "SUBPARTITION" if expression.args.get("subpartition") else "PARTITION" 1633 return f"{partition_keyword}({self.expressions(expression, flat=True)})" 1634 1635 def properties_sql(self, expression: exp.Properties) -> str: 1636 root_properties = [] 1637 with_properties = [] 1638 1639 for p in expression.expressions: 1640 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1641 if p_loc == exp.Properties.Location.POST_WITH: 1642 with_properties.append(p) 1643 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1644 root_properties.append(p) 1645 1646 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1647 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1648 1649 if root_props and with_props and not self.pretty: 1650 with_props = " " + with_props 1651 1652 return root_props + with_props 1653 1654 def root_properties(self, properties: exp.Properties) -> str: 1655 if properties.expressions: 1656 return self.expressions(properties, indent=False, sep=" ") 1657 return "" 1658 1659 def properties( 1660 self, 1661 properties: exp.Properties, 1662 prefix: str = "", 1663 sep: str = ", ", 1664 suffix: str = "", 1665 wrapped: bool = True, 1666 ) -> str: 1667 if properties.expressions: 1668 expressions = self.expressions(properties, sep=sep, indent=False) 1669 if expressions: 1670 expressions = self.wrap(expressions) if wrapped else expressions 1671 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1672 return "" 1673 1674 def with_properties(self, properties: exp.Properties) -> str: 1675 return self.properties(properties, prefix=self.seg(self.WITH_PROPERTIES_PREFIX, sep="")) 1676 1677 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1678 properties_locs = defaultdict(list) 1679 for p in properties.expressions: 1680 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1681 if p_loc != exp.Properties.Location.UNSUPPORTED: 1682 properties_locs[p_loc].append(p) 1683 else: 1684 self.unsupported(f"Unsupported property {p.key}") 1685 1686 return properties_locs 1687 1688 def property_name(self, expression: exp.Property, string_key: bool = False) -> str: 1689 if isinstance(expression.this, exp.Dot): 1690 return self.sql(expression, "this") 1691 return f"'{expression.name}'" if string_key else expression.name 1692 1693 def property_sql(self, expression: exp.Property) -> str: 1694 property_cls = expression.__class__ 1695 if property_cls == exp.Property: 1696 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1697 1698 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1699 if not property_name: 1700 self.unsupported(f"Unsupported property {expression.key}") 1701 1702 return f"{property_name}={self.sql(expression, 'this')}" 1703 1704 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1705 if self.SUPPORTS_CREATE_TABLE_LIKE: 1706 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1707 options = f" {options}" if options else "" 1708 1709 like = f"LIKE {self.sql(expression, 'this')}{options}" 1710 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1711 like = f"({like})" 1712 1713 return like 1714 1715 if expression.expressions: 1716 self.unsupported("Transpilation of LIKE property options is unsupported") 1717 1718 select = exp.select("*").from_(expression.this).limit(0) 1719 return f"AS {self.sql(select)}" 1720 1721 def fallbackproperty_sql(self, expression: exp.FallbackProperty) -> str: 1722 no = "NO " if expression.args.get("no") else "" 1723 protection = " PROTECTION" if expression.args.get("protection") else "" 1724 return f"{no}FALLBACK{protection}" 1725 1726 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1727 no = "NO " if expression.args.get("no") else "" 1728 local = expression.args.get("local") 1729 local = f"{local} " if local else "" 1730 dual = "DUAL " if expression.args.get("dual") else "" 1731 before = "BEFORE " if expression.args.get("before") else "" 1732 after = "AFTER " if expression.args.get("after") else "" 1733 return f"{no}{local}{dual}{before}{after}JOURNAL" 1734 1735 def freespaceproperty_sql(self, expression: exp.FreespaceProperty) -> str: 1736 freespace = self.sql(expression, "this") 1737 percent = " PERCENT" if expression.args.get("percent") else "" 1738 return f"FREESPACE={freespace}{percent}" 1739 1740 def checksumproperty_sql(self, expression: exp.ChecksumProperty) -> str: 1741 if expression.args.get("default"): 1742 property = "DEFAULT" 1743 elif expression.args.get("on"): 1744 property = "ON" 1745 else: 1746 property = "OFF" 1747 return f"CHECKSUM={property}" 1748 1749 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1750 if expression.args.get("no"): 1751 return "NO MERGEBLOCKRATIO" 1752 if expression.args.get("default"): 1753 return "DEFAULT MERGEBLOCKRATIO" 1754 1755 percent = " PERCENT" if expression.args.get("percent") else "" 1756 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}" 1757 1758 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1759 default = expression.args.get("default") 1760 minimum = expression.args.get("minimum") 1761 maximum = expression.args.get("maximum") 1762 if default or minimum or maximum: 1763 if default: 1764 prop = "DEFAULT" 1765 elif minimum: 1766 prop = "MINIMUM" 1767 else: 1768 prop = "MAXIMUM" 1769 return f"{prop} DATABLOCKSIZE" 1770 units = expression.args.get("units") 1771 units = f" {units}" if units else "" 1772 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}" 1773 1774 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1775 autotemp = expression.args.get("autotemp") 1776 always = expression.args.get("always") 1777 default = expression.args.get("default") 1778 manual = expression.args.get("manual") 1779 never = expression.args.get("never") 1780 1781 if autotemp is not None: 1782 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1783 elif always: 1784 prop = "ALWAYS" 1785 elif default: 1786 prop = "DEFAULT" 1787 elif manual: 1788 prop = "MANUAL" 1789 elif never: 1790 prop = "NEVER" 1791 return f"BLOCKCOMPRESSION={prop}" 1792 1793 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1794 no = expression.args.get("no") 1795 no = " NO" if no else "" 1796 concurrent = expression.args.get("concurrent") 1797 concurrent = " CONCURRENT" if concurrent else "" 1798 target = self.sql(expression, "target") 1799 target = f" {target}" if target else "" 1800 return f"WITH{no}{concurrent} ISOLATED LOADING{target}" 1801 1802 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1803 if isinstance(expression.this, list): 1804 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1805 if expression.this: 1806 modulus = self.sql(expression, "this") 1807 remainder = self.sql(expression, "expression") 1808 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1809 1810 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1811 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1812 return f"FROM ({from_expressions}) TO ({to_expressions})" 1813 1814 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1815 this = self.sql(expression, "this") 1816 1817 for_values_or_default = expression.expression 1818 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1819 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1820 else: 1821 for_values_or_default = " DEFAULT" 1822 1823 return f"PARTITION OF {this}{for_values_or_default}" 1824 1825 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1826 kind = expression.args.get("kind") 1827 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1828 for_or_in = expression.args.get("for_or_in") 1829 for_or_in = f" {for_or_in}" if for_or_in else "" 1830 lock_type = expression.args.get("lock_type") 1831 override = " OVERRIDE" if expression.args.get("override") else "" 1832 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}" 1833 1834 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1835 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1836 statistics = expression.args.get("statistics") 1837 statistics_sql = "" 1838 if statistics is not None: 1839 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1840 return f"{data_sql}{statistics_sql}" 1841 1842 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1843 this = self.sql(expression, "this") 1844 this = f"HISTORY_TABLE={this}" if this else "" 1845 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1846 data_consistency = ( 1847 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1848 ) 1849 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1850 retention_period = ( 1851 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1852 ) 1853 1854 if this: 1855 on_sql = self.func("ON", this, data_consistency, retention_period) 1856 else: 1857 on_sql = "ON" if expression.args.get("on") else "OFF" 1858 1859 sql = f"SYSTEM_VERSIONING={on_sql}" 1860 1861 return f"WITH({sql})" if expression.args.get("with") else sql 1862 1863 def insert_sql(self, expression: exp.Insert) -> str: 1864 hint = self.sql(expression, "hint") 1865 overwrite = expression.args.get("overwrite") 1866 1867 if isinstance(expression.this, exp.Directory): 1868 this = " OVERWRITE" if overwrite else " INTO" 1869 else: 1870 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1871 1872 stored = self.sql(expression, "stored") 1873 stored = f" {stored}" if stored else "" 1874 alternative = expression.args.get("alternative") 1875 alternative = f" OR {alternative}" if alternative else "" 1876 ignore = " IGNORE" if expression.args.get("ignore") else "" 1877 is_function = expression.args.get("is_function") 1878 if is_function: 1879 this = f"{this} FUNCTION" 1880 this = f"{this} {self.sql(expression, 'this')}" 1881 1882 exists = " IF EXISTS" if expression.args.get("exists") else "" 1883 where = self.sql(expression, "where") 1884 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1885 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1886 on_conflict = self.sql(expression, "conflict") 1887 on_conflict = f" {on_conflict}" if on_conflict else "" 1888 by_name = " BY NAME" if expression.args.get("by_name") else "" 1889 returning = self.sql(expression, "returning") 1890 1891 if self.RETURNING_END: 1892 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1893 else: 1894 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1895 1896 partition_by = self.sql(expression, "partition") 1897 partition_by = f" {partition_by}" if partition_by else "" 1898 settings = self.sql(expression, "settings") 1899 settings = f" {settings}" if settings else "" 1900 1901 source = self.sql(expression, "source") 1902 source = f"TABLE {source}" if source else "" 1903 1904 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1905 return self.prepend_ctes(expression, sql) 1906 1907 def introducer_sql(self, expression: exp.Introducer) -> str: 1908 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 1909 1910 def kill_sql(self, expression: exp.Kill) -> str: 1911 kind = self.sql(expression, "kind") 1912 kind = f" {kind}" if kind else "" 1913 this = self.sql(expression, "this") 1914 this = f" {this}" if this else "" 1915 return f"KILL{kind}{this}" 1916 1917 def pseudotype_sql(self, expression: exp.PseudoType) -> str: 1918 return expression.name 1919 1920 def objectidentifier_sql(self, expression: exp.ObjectIdentifier) -> str: 1921 return expression.name 1922 1923 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1924 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1925 1926 constraint = self.sql(expression, "constraint") 1927 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1928 1929 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1930 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1931 action = self.sql(expression, "action") 1932 1933 expressions = self.expressions(expression, flat=True) 1934 if expressions: 1935 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1936 expressions = f" {set_keyword}{expressions}" 1937 1938 where = self.sql(expression, "where") 1939 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}" 1940 1941 def returning_sql(self, expression: exp.Returning) -> str: 1942 return f"{self.seg('RETURNING')} {self.expressions(expression, flat=True)}" 1943 1944 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1945 fields = self.sql(expression, "fields") 1946 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1947 escaped = self.sql(expression, "escaped") 1948 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1949 items = self.sql(expression, "collection_items") 1950 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1951 keys = self.sql(expression, "map_keys") 1952 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1953 lines = self.sql(expression, "lines") 1954 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1955 null = self.sql(expression, "null") 1956 null = f" NULL DEFINED AS {null}" if null else "" 1957 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}" 1958 1959 def withtablehint_sql(self, expression: exp.WithTableHint) -> str: 1960 return f"WITH ({self.expressions(expression, flat=True)})" 1961 1962 def indextablehint_sql(self, expression: exp.IndexTableHint) -> str: 1963 this = f"{self.sql(expression, 'this')} INDEX" 1964 target = self.sql(expression, "target") 1965 target = f" FOR {target}" if target else "" 1966 return f"{this}{target} ({self.expressions(expression, flat=True)})" 1967 1968 def historicaldata_sql(self, expression: exp.HistoricalData) -> str: 1969 this = self.sql(expression, "this") 1970 kind = self.sql(expression, "kind") 1971 expr = self.sql(expression, "expression") 1972 return f"{this} ({kind} => {expr})" 1973 1974 def table_parts(self, expression: exp.Table) -> str: 1975 return ".".join( 1976 self.sql(part) 1977 for part in ( 1978 expression.args.get("catalog"), 1979 expression.args.get("db"), 1980 expression.args.get("this"), 1981 ) 1982 if part is not None 1983 ) 1984 1985 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1986 table = self.table_parts(expression) 1987 only = "ONLY " if expression.args.get("only") else "" 1988 partition = self.sql(expression, "partition") 1989 partition = f" {partition}" if partition else "" 1990 version = self.sql(expression, "version") 1991 version = f" {version}" if version else "" 1992 alias = self.sql(expression, "alias") 1993 alias = f"{sep}{alias}" if alias else "" 1994 1995 sample = self.sql(expression, "sample") 1996 if self.dialect.ALIAS_POST_TABLESAMPLE: 1997 sample_pre_alias = sample 1998 sample_post_alias = "" 1999 else: 2000 sample_pre_alias = "" 2001 sample_post_alias = sample 2002 2003 hints = self.expressions(expression, key="hints", sep=" ") 2004 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2005 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2006 joins = self.indent( 2007 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2008 ) 2009 laterals = self.expressions(expression, key="laterals", sep="") 2010 2011 file_format = self.sql(expression, "format") 2012 if file_format: 2013 pattern = self.sql(expression, "pattern") 2014 pattern = f", PATTERN => {pattern}" if pattern else "" 2015 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2016 2017 ordinality = expression.args.get("ordinality") or "" 2018 if ordinality: 2019 ordinality = f" WITH ORDINALITY{alias}" 2020 alias = "" 2021 2022 when = self.sql(expression, "when") 2023 if when: 2024 table = f"{table} {when}" 2025 2026 changes = self.sql(expression, "changes") 2027 changes = f" {changes}" if changes else "" 2028 2029 rows_from = self.expressions(expression, key="rows_from") 2030 if rows_from: 2031 table = f"ROWS FROM {self.wrap(rows_from)}" 2032 2033 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}" 2034 2035 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2036 table = self.func("TABLE", expression.this) 2037 alias = self.sql(expression, "alias") 2038 alias = f" AS {alias}" if alias else "" 2039 sample = self.sql(expression, "sample") 2040 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2041 joins = self.indent( 2042 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2043 ) 2044 return f"{table}{alias}{pivots}{sample}{joins}" 2045 2046 def tablesample_sql( 2047 self, 2048 expression: exp.TableSample, 2049 tablesample_keyword: t.Optional[str] = None, 2050 ) -> str: 2051 method = self.sql(expression, "method") 2052 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2053 numerator = self.sql(expression, "bucket_numerator") 2054 denominator = self.sql(expression, "bucket_denominator") 2055 field = self.sql(expression, "bucket_field") 2056 field = f" ON {field}" if field else "" 2057 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2058 seed = self.sql(expression, "seed") 2059 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2060 2061 size = self.sql(expression, "size") 2062 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2063 size = f"{size} ROWS" 2064 2065 percent = self.sql(expression, "percent") 2066 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2067 percent = f"{percent} PERCENT" 2068 2069 expr = f"{bucket}{percent}{size}" 2070 if self.TABLESAMPLE_REQUIRES_PARENS: 2071 expr = f"({expr})" 2072 2073 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}" 2074 2075 def pivot_sql(self, expression: exp.Pivot) -> str: 2076 expressions = self.expressions(expression, flat=True) 2077 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2078 2079 group = self.sql(expression, "group") 2080 2081 if expression.this: 2082 this = self.sql(expression, "this") 2083 if not expressions: 2084 return f"UNPIVOT {this}" 2085 2086 on = f"{self.seg('ON')} {expressions}" 2087 into = self.sql(expression, "into") 2088 into = f"{self.seg('INTO')} {into}" if into else "" 2089 using = self.expressions(expression, key="using", flat=True) 2090 using = f"{self.seg('USING')} {using}" if using else "" 2091 return f"{direction} {this}{on}{into}{using}{group}" 2092 2093 alias = self.sql(expression, "alias") 2094 alias = f" AS {alias}" if alias else "" 2095 2096 fields = self.expressions( 2097 expression, 2098 "fields", 2099 sep=" ", 2100 dynamic=True, 2101 new_line=True, 2102 skip_first=True, 2103 skip_last=True, 2104 ) 2105 2106 include_nulls = expression.args.get("include_nulls") 2107 if include_nulls is not None: 2108 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2109 else: 2110 nulls = "" 2111 2112 default_on_null = self.sql(expression, "default_on_null") 2113 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2114 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}" 2115 2116 def version_sql(self, expression: exp.Version) -> str: 2117 this = f"FOR {expression.name}" 2118 kind = expression.text("kind") 2119 expr = self.sql(expression, "expression") 2120 return f"{this} {kind} {expr}" 2121 2122 def tuple_sql(self, expression: exp.Tuple) -> str: 2123 return f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 2124 2125 def update_sql(self, expression: exp.Update) -> str: 2126 this = self.sql(expression, "this") 2127 set_sql = self.expressions(expression, flat=True) 2128 from_sql = self.sql(expression, "from") 2129 where_sql = self.sql(expression, "where") 2130 returning = self.sql(expression, "returning") 2131 order = self.sql(expression, "order") 2132 limit = self.sql(expression, "limit") 2133 if self.RETURNING_END: 2134 expression_sql = f"{from_sql}{where_sql}{returning}" 2135 else: 2136 expression_sql = f"{returning}{from_sql}{where_sql}" 2137 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2138 return self.prepend_ctes(expression, sql) 2139 2140 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2141 values_as_table = values_as_table and self.VALUES_AS_TABLE 2142 2143 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2144 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2145 args = self.expressions(expression) 2146 alias = self.sql(expression, "alias") 2147 values = f"VALUES{self.seg('')}{args}" 2148 values = ( 2149 f"({values})" 2150 if self.WRAP_DERIVED_VALUES 2151 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2152 else values 2153 ) 2154 return f"{values} AS {alias}" if alias else values 2155 2156 # Converts `VALUES...` expression into a series of select unions. 2157 alias_node = expression.args.get("alias") 2158 column_names = alias_node and alias_node.columns 2159 2160 selects: t.List[exp.Query] = [] 2161 2162 for i, tup in enumerate(expression.expressions): 2163 row = tup.expressions 2164 2165 if i == 0 and column_names: 2166 row = [ 2167 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2168 ] 2169 2170 selects.append(exp.Select(expressions=row)) 2171 2172 if self.pretty: 2173 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2174 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2175 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2176 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2177 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2178 2179 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2180 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2181 return f"({unions}){alias}" 2182 2183 def var_sql(self, expression: exp.Var) -> str: 2184 return self.sql(expression, "this") 2185 2186 @unsupported_args("expressions") 2187 def into_sql(self, expression: exp.Into) -> str: 2188 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2189 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2190 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}" 2191 2192 def from_sql(self, expression: exp.From) -> str: 2193 return f"{self.seg('FROM')} {self.sql(expression, 'this')}" 2194 2195 def groupingsets_sql(self, expression: exp.GroupingSets) -> str: 2196 grouping_sets = self.expressions(expression, indent=False) 2197 return f"GROUPING SETS {self.wrap(grouping_sets)}" 2198 2199 def rollup_sql(self, expression: exp.Rollup) -> str: 2200 expressions = self.expressions(expression, indent=False) 2201 return f"ROLLUP {self.wrap(expressions)}" if expressions else "WITH ROLLUP" 2202 2203 def cube_sql(self, expression: exp.Cube) -> str: 2204 expressions = self.expressions(expression, indent=False) 2205 return f"CUBE {self.wrap(expressions)}" if expressions else "WITH CUBE" 2206 2207 def group_sql(self, expression: exp.Group) -> str: 2208 group_by_all = expression.args.get("all") 2209 if group_by_all is True: 2210 modifier = " ALL" 2211 elif group_by_all is False: 2212 modifier = " DISTINCT" 2213 else: 2214 modifier = "" 2215 2216 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2217 2218 grouping_sets = self.expressions(expression, key="grouping_sets") 2219 cube = self.expressions(expression, key="cube") 2220 rollup = self.expressions(expression, key="rollup") 2221 2222 groupings = csv( 2223 self.seg(grouping_sets) if grouping_sets else "", 2224 self.seg(cube) if cube else "", 2225 self.seg(rollup) if rollup else "", 2226 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2227 sep=self.GROUPINGS_SEP, 2228 ) 2229 2230 if ( 2231 expression.expressions 2232 and groupings 2233 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2234 ): 2235 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2236 2237 return f"{group_by}{groupings}" 2238 2239 def having_sql(self, expression: exp.Having) -> str: 2240 this = self.indent(self.sql(expression, "this")) 2241 return f"{self.seg('HAVING')}{self.sep()}{this}" 2242 2243 def connect_sql(self, expression: exp.Connect) -> str: 2244 start = self.sql(expression, "start") 2245 start = self.seg(f"START WITH {start}") if start else "" 2246 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2247 connect = self.sql(expression, "connect") 2248 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2249 return start + connect 2250 2251 def prior_sql(self, expression: exp.Prior) -> str: 2252 return f"PRIOR {self.sql(expression, 'this')}" 2253 2254 def join_sql(self, expression: exp.Join) -> str: 2255 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2256 side = None 2257 else: 2258 side = expression.side 2259 2260 op_sql = " ".join( 2261 op 2262 for op in ( 2263 expression.method, 2264 "GLOBAL" if expression.args.get("global") else None, 2265 side, 2266 expression.kind, 2267 expression.hint if self.JOIN_HINTS else None, 2268 ) 2269 if op 2270 ) 2271 match_cond = self.sql(expression, "match_condition") 2272 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2273 on_sql = self.sql(expression, "on") 2274 using = expression.args.get("using") 2275 2276 if not on_sql and using: 2277 on_sql = csv(*(self.sql(column) for column in using)) 2278 2279 this = expression.this 2280 this_sql = self.sql(this) 2281 2282 exprs = self.expressions(expression) 2283 if exprs: 2284 this_sql = f"{this_sql},{self.seg(exprs)}" 2285 2286 if on_sql: 2287 on_sql = self.indent(on_sql, skip_first=True) 2288 space = self.seg(" " * self.pad) if self.pretty else " " 2289 if using: 2290 on_sql = f"{space}USING ({on_sql})" 2291 else: 2292 on_sql = f"{space}ON {on_sql}" 2293 elif not op_sql: 2294 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2295 return f" {this_sql}" 2296 2297 return f", {this_sql}" 2298 2299 if op_sql != "STRAIGHT_JOIN": 2300 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2301 2302 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2303 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}" 2304 2305 def lambda_sql(self, expression: exp.Lambda, arrow_sep: str = "->") -> str: 2306 args = self.expressions(expression, flat=True) 2307 args = f"({args})" if len(args.split(",")) > 1 else args 2308 return f"{args} {arrow_sep} {self.sql(expression, 'this')}" 2309 2310 def lateral_op(self, expression: exp.Lateral) -> str: 2311 cross_apply = expression.args.get("cross_apply") 2312 2313 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2314 if cross_apply is True: 2315 op = "INNER JOIN " 2316 elif cross_apply is False: 2317 op = "LEFT JOIN " 2318 else: 2319 op = "" 2320 2321 return f"{op}LATERAL" 2322 2323 def lateral_sql(self, expression: exp.Lateral) -> str: 2324 this = self.sql(expression, "this") 2325 2326 if expression.args.get("view"): 2327 alias = expression.args["alias"] 2328 columns = self.expressions(alias, key="columns", flat=True) 2329 table = f" {alias.name}" if alias.name else "" 2330 columns = f" AS {columns}" if columns else "" 2331 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2332 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2333 2334 alias = self.sql(expression, "alias") 2335 alias = f" AS {alias}" if alias else "" 2336 2337 ordinality = expression.args.get("ordinality") or "" 2338 if ordinality: 2339 ordinality = f" WITH ORDINALITY{alias}" 2340 alias = "" 2341 2342 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}" 2343 2344 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2345 this = self.sql(expression, "this") 2346 2347 args = [ 2348 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2349 for e in (expression.args.get(k) for k in ("offset", "expression")) 2350 if e 2351 ] 2352 2353 args_sql = ", ".join(self.sql(e) for e in args) 2354 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2355 expressions = self.expressions(expression, flat=True) 2356 limit_options = self.sql(expression, "limit_options") 2357 expressions = f" BY {expressions}" if expressions else "" 2358 2359 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}" 2360 2361 def offset_sql(self, expression: exp.Offset) -> str: 2362 this = self.sql(expression, "this") 2363 value = expression.expression 2364 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2365 expressions = self.expressions(expression, flat=True) 2366 expressions = f" BY {expressions}" if expressions else "" 2367 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}" 2368 2369 def setitem_sql(self, expression: exp.SetItem) -> str: 2370 kind = self.sql(expression, "kind") 2371 kind = f"{kind} " if kind else "" 2372 this = self.sql(expression, "this") 2373 expressions = self.expressions(expression) 2374 collate = self.sql(expression, "collate") 2375 collate = f" COLLATE {collate}" if collate else "" 2376 global_ = "GLOBAL " if expression.args.get("global") else "" 2377 return f"{global_}{kind}{this}{expressions}{collate}" 2378 2379 def set_sql(self, expression: exp.Set) -> str: 2380 expressions = f" {self.expressions(expression, flat=True)}" 2381 tag = " TAG" if expression.args.get("tag") else "" 2382 return f"{'UNSET' if expression.args.get('unset') else 'SET'}{tag}{expressions}" 2383 2384 def pragma_sql(self, expression: exp.Pragma) -> str: 2385 return f"PRAGMA {self.sql(expression, 'this')}" 2386 2387 def lock_sql(self, expression: exp.Lock) -> str: 2388 if not self.LOCKING_READS_SUPPORTED: 2389 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2390 return "" 2391 2392 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2393 expressions = self.expressions(expression, flat=True) 2394 expressions = f" OF {expressions}" if expressions else "" 2395 wait = expression.args.get("wait") 2396 2397 if wait is not None: 2398 if isinstance(wait, exp.Literal): 2399 wait = f" WAIT {self.sql(wait)}" 2400 else: 2401 wait = " NOWAIT" if wait else " SKIP LOCKED" 2402 2403 return f"{lock_type}{expressions}{wait or ''}" 2404 2405 def literal_sql(self, expression: exp.Literal) -> str: 2406 text = expression.this or "" 2407 if expression.is_string: 2408 text = f"{self.dialect.QUOTE_START}{self.escape_str(text)}{self.dialect.QUOTE_END}" 2409 return text 2410 2411 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2412 if self.dialect.ESCAPED_SEQUENCES: 2413 to_escaped = self.dialect.ESCAPED_SEQUENCES 2414 text = "".join( 2415 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2416 ) 2417 2418 return self._replace_line_breaks(text).replace( 2419 self.dialect.QUOTE_END, self._escaped_quote_end 2420 ) 2421 2422 def loaddata_sql(self, expression: exp.LoadData) -> str: 2423 local = " LOCAL" if expression.args.get("local") else "" 2424 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2425 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2426 this = f" INTO TABLE {self.sql(expression, 'this')}" 2427 partition = self.sql(expression, "partition") 2428 partition = f" {partition}" if partition else "" 2429 input_format = self.sql(expression, "input_format") 2430 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2431 serde = self.sql(expression, "serde") 2432 serde = f" SERDE {serde}" if serde else "" 2433 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}" 2434 2435 def null_sql(self, *_) -> str: 2436 return "NULL" 2437 2438 def boolean_sql(self, expression: exp.Boolean) -> str: 2439 return "TRUE" if expression.this else "FALSE" 2440 2441 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2442 this = self.sql(expression, "this") 2443 this = f"{this} " if this else this 2444 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2445 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore 2446 2447 def withfill_sql(self, expression: exp.WithFill) -> str: 2448 from_sql = self.sql(expression, "from") 2449 from_sql = f" FROM {from_sql}" if from_sql else "" 2450 to_sql = self.sql(expression, "to") 2451 to_sql = f" TO {to_sql}" if to_sql else "" 2452 step_sql = self.sql(expression, "step") 2453 step_sql = f" STEP {step_sql}" if step_sql else "" 2454 interpolated_values = [ 2455 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2456 if isinstance(e, exp.Alias) 2457 else self.sql(e, "this") 2458 for e in expression.args.get("interpolate") or [] 2459 ] 2460 interpolate = ( 2461 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2462 ) 2463 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}" 2464 2465 def cluster_sql(self, expression: exp.Cluster) -> str: 2466 return self.op_expressions("CLUSTER BY", expression) 2467 2468 def distribute_sql(self, expression: exp.Distribute) -> str: 2469 return self.op_expressions("DISTRIBUTE BY", expression) 2470 2471 def sort_sql(self, expression: exp.Sort) -> str: 2472 return self.op_expressions("SORT BY", expression) 2473 2474 def ordered_sql(self, expression: exp.Ordered) -> str: 2475 desc = expression.args.get("desc") 2476 asc = not desc 2477 2478 nulls_first = expression.args.get("nulls_first") 2479 nulls_last = not nulls_first 2480 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2481 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2482 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2483 2484 this = self.sql(expression, "this") 2485 2486 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2487 nulls_sort_change = "" 2488 if nulls_first and ( 2489 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2490 ): 2491 nulls_sort_change = " NULLS FIRST" 2492 elif ( 2493 nulls_last 2494 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2495 and not nulls_are_last 2496 ): 2497 nulls_sort_change = " NULLS LAST" 2498 2499 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2500 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2501 window = expression.find_ancestor(exp.Window, exp.Select) 2502 if isinstance(window, exp.Window) and window.args.get("spec"): 2503 self.unsupported( 2504 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2505 ) 2506 nulls_sort_change = "" 2507 elif self.NULL_ORDERING_SUPPORTED is False and ( 2508 (asc and nulls_sort_change == " NULLS LAST") 2509 or (desc and nulls_sort_change == " NULLS FIRST") 2510 ): 2511 # BigQuery does not allow these ordering/nulls combinations when used under 2512 # an aggregation func or under a window containing one 2513 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2514 2515 if isinstance(ancestor, exp.Window): 2516 ancestor = ancestor.this 2517 if isinstance(ancestor, exp.AggFunc): 2518 self.unsupported( 2519 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2520 ) 2521 nulls_sort_change = "" 2522 elif self.NULL_ORDERING_SUPPORTED is None: 2523 if expression.this.is_int: 2524 self.unsupported( 2525 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2526 ) 2527 elif not isinstance(expression.this, exp.Rand): 2528 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2529 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2530 nulls_sort_change = "" 2531 2532 with_fill = self.sql(expression, "with_fill") 2533 with_fill = f" {with_fill}" if with_fill else "" 2534 2535 return f"{this}{sort_order}{nulls_sort_change}{with_fill}" 2536 2537 def matchrecognizemeasure_sql(self, expression: exp.MatchRecognizeMeasure) -> str: 2538 window_frame = self.sql(expression, "window_frame") 2539 window_frame = f"{window_frame} " if window_frame else "" 2540 2541 this = self.sql(expression, "this") 2542 2543 return f"{window_frame}{this}" 2544 2545 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2546 partition = self.partition_by_sql(expression) 2547 order = self.sql(expression, "order") 2548 measures = self.expressions(expression, key="measures") 2549 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2550 rows = self.sql(expression, "rows") 2551 rows = self.seg(rows) if rows else "" 2552 after = self.sql(expression, "after") 2553 after = self.seg(after) if after else "" 2554 pattern = self.sql(expression, "pattern") 2555 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2556 definition_sqls = [ 2557 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2558 for definition in expression.args.get("define", []) 2559 ] 2560 definitions = self.expressions(sqls=definition_sqls) 2561 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2562 body = "".join( 2563 ( 2564 partition, 2565 order, 2566 measures, 2567 rows, 2568 after, 2569 pattern, 2570 define, 2571 ) 2572 ) 2573 alias = self.sql(expression, "alias") 2574 alias = f" {alias}" if alias else "" 2575 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}" 2576 2577 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2578 limit = expression.args.get("limit") 2579 2580 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2581 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2582 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2583 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2584 2585 return csv( 2586 *sqls, 2587 *[self.sql(join) for join in expression.args.get("joins") or []], 2588 self.sql(expression, "match"), 2589 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2590 self.sql(expression, "prewhere"), 2591 self.sql(expression, "where"), 2592 self.sql(expression, "connect"), 2593 self.sql(expression, "group"), 2594 self.sql(expression, "having"), 2595 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2596 self.sql(expression, "order"), 2597 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2598 *self.after_limit_modifiers(expression), 2599 self.options_modifier(expression), 2600 sep="", 2601 ) 2602 2603 def options_modifier(self, expression: exp.Expression) -> str: 2604 options = self.expressions(expression, key="options") 2605 return f" {options}" if options else "" 2606 2607 def queryoption_sql(self, expression: exp.QueryOption) -> str: 2608 self.unsupported("Unsupported query option.") 2609 return "" 2610 2611 def offset_limit_modifiers( 2612 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2613 ) -> t.List[str]: 2614 return [ 2615 self.sql(expression, "offset") if fetch else self.sql(limit), 2616 self.sql(limit) if fetch else self.sql(expression, "offset"), 2617 ] 2618 2619 def after_limit_modifiers(self, expression: exp.Expression) -> t.List[str]: 2620 locks = self.expressions(expression, key="locks", sep=" ") 2621 locks = f" {locks}" if locks else "" 2622 return [locks, self.sql(expression, "sample")] 2623 2624 def select_sql(self, expression: exp.Select) -> str: 2625 into = expression.args.get("into") 2626 if not self.SUPPORTS_SELECT_INTO and into: 2627 into.pop() 2628 2629 hint = self.sql(expression, "hint") 2630 distinct = self.sql(expression, "distinct") 2631 distinct = f" {distinct}" if distinct else "" 2632 kind = self.sql(expression, "kind") 2633 2634 limit = expression.args.get("limit") 2635 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2636 top = self.limit_sql(limit, top=True) 2637 limit.pop() 2638 else: 2639 top = "" 2640 2641 expressions = self.expressions(expression) 2642 2643 if kind: 2644 if kind in self.SELECT_KINDS: 2645 kind = f" AS {kind}" 2646 else: 2647 if kind == "STRUCT": 2648 expressions = self.expressions( 2649 sqls=[ 2650 self.sql( 2651 exp.Struct( 2652 expressions=[ 2653 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2654 if isinstance(e, exp.Alias) 2655 else e 2656 for e in expression.expressions 2657 ] 2658 ) 2659 ) 2660 ] 2661 ) 2662 kind = "" 2663 2664 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2665 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2666 2667 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2668 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2669 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2670 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2671 sql = self.query_modifiers( 2672 expression, 2673 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2674 self.sql(expression, "into", comment=False), 2675 self.sql(expression, "from", comment=False), 2676 ) 2677 2678 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2679 if expression.args.get("with"): 2680 sql = self.maybe_comment(sql, expression) 2681 expression.pop_comments() 2682 2683 sql = self.prepend_ctes(expression, sql) 2684 2685 if not self.SUPPORTS_SELECT_INTO and into: 2686 if into.args.get("temporary"): 2687 table_kind = " TEMPORARY" 2688 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2689 table_kind = " UNLOGGED" 2690 else: 2691 table_kind = "" 2692 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2693 2694 return sql 2695 2696 def schema_sql(self, expression: exp.Schema) -> str: 2697 this = self.sql(expression, "this") 2698 sql = self.schema_columns_sql(expression) 2699 return f"{this} {sql}" if this and sql else this or sql 2700 2701 def schema_columns_sql(self, expression: exp.Schema) -> str: 2702 if expression.expressions: 2703 return f"({self.sep('')}{self.expressions(expression)}{self.seg(')', sep='')}" 2704 return "" 2705 2706 def star_sql(self, expression: exp.Star) -> str: 2707 except_ = self.expressions(expression, key="except", flat=True) 2708 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2709 replace = self.expressions(expression, key="replace", flat=True) 2710 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2711 rename = self.expressions(expression, key="rename", flat=True) 2712 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2713 return f"*{except_}{replace}{rename}" 2714 2715 def parameter_sql(self, expression: exp.Parameter) -> str: 2716 this = self.sql(expression, "this") 2717 return f"{self.PARAMETER_TOKEN}{this}" 2718 2719 def sessionparameter_sql(self, expression: exp.SessionParameter) -> str: 2720 this = self.sql(expression, "this") 2721 kind = expression.text("kind") 2722 if kind: 2723 kind = f"{kind}." 2724 return f"@@{kind}{this}" 2725 2726 def placeholder_sql(self, expression: exp.Placeholder) -> str: 2727 return f"{self.NAMED_PLACEHOLDER_TOKEN}{expression.name}" if expression.this else "?" 2728 2729 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2730 alias = self.sql(expression, "alias") 2731 alias = f"{sep}{alias}" if alias else "" 2732 sample = self.sql(expression, "sample") 2733 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2734 alias = f"{sample}{alias}" 2735 2736 # Set to None so it's not generated again by self.query_modifiers() 2737 expression.set("sample", None) 2738 2739 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2740 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2741 return self.prepend_ctes(expression, sql) 2742 2743 def qualify_sql(self, expression: exp.Qualify) -> str: 2744 this = self.indent(self.sql(expression, "this")) 2745 return f"{self.seg('QUALIFY')}{self.sep()}{this}" 2746 2747 def unnest_sql(self, expression: exp.Unnest) -> str: 2748 args = self.expressions(expression, flat=True) 2749 2750 alias = expression.args.get("alias") 2751 offset = expression.args.get("offset") 2752 2753 if self.UNNEST_WITH_ORDINALITY: 2754 if alias and isinstance(offset, exp.Expression): 2755 alias.append("columns", offset) 2756 2757 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2758 columns = alias.columns 2759 alias = self.sql(columns[0]) if columns else "" 2760 else: 2761 alias = self.sql(alias) 2762 2763 alias = f" AS {alias}" if alias else alias 2764 if self.UNNEST_WITH_ORDINALITY: 2765 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2766 else: 2767 if isinstance(offset, exp.Expression): 2768 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2769 elif offset: 2770 suffix = f"{alias} WITH OFFSET" 2771 else: 2772 suffix = alias 2773 2774 return f"UNNEST({args}){suffix}" 2775 2776 def prewhere_sql(self, expression: exp.PreWhere) -> str: 2777 return "" 2778 2779 def where_sql(self, expression: exp.Where) -> str: 2780 this = self.indent(self.sql(expression, "this")) 2781 return f"{self.seg('WHERE')}{self.sep()}{this}" 2782 2783 def window_sql(self, expression: exp.Window) -> str: 2784 this = self.sql(expression, "this") 2785 partition = self.partition_by_sql(expression) 2786 order = expression.args.get("order") 2787 order = self.order_sql(order, flat=True) if order else "" 2788 spec = self.sql(expression, "spec") 2789 alias = self.sql(expression, "alias") 2790 over = self.sql(expression, "over") or "OVER" 2791 2792 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2793 2794 first = expression.args.get("first") 2795 if first is None: 2796 first = "" 2797 else: 2798 first = "FIRST" if first else "LAST" 2799 2800 if not partition and not order and not spec and alias: 2801 return f"{this} {alias}" 2802 2803 args = self.format_args( 2804 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2805 ) 2806 return f"{this} ({args})" 2807 2808 def partition_by_sql(self, expression: exp.Window | exp.MatchRecognize) -> str: 2809 partition = self.expressions(expression, key="partition_by", flat=True) 2810 return f"PARTITION BY {partition}" if partition else "" 2811 2812 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2813 kind = self.sql(expression, "kind") 2814 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2815 end = ( 2816 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2817 or "CURRENT ROW" 2818 ) 2819 2820 window_spec = f"{kind} BETWEEN {start} AND {end}" 2821 2822 exclude = self.sql(expression, "exclude") 2823 if exclude: 2824 if self.SUPPORTS_WINDOW_EXCLUDE: 2825 window_spec += f" EXCLUDE {exclude}" 2826 else: 2827 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2828 2829 return window_spec 2830 2831 def withingroup_sql(self, expression: exp.WithinGroup) -> str: 2832 this = self.sql(expression, "this") 2833 expression_sql = self.sql(expression, "expression")[1:] # order has a leading space 2834 return f"{this} WITHIN GROUP ({expression_sql})" 2835 2836 def between_sql(self, expression: exp.Between) -> str: 2837 this = self.sql(expression, "this") 2838 low = self.sql(expression, "low") 2839 high = self.sql(expression, "high") 2840 return f"{this} BETWEEN {low} AND {high}" 2841 2842 def bracket_offset_expressions( 2843 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2844 ) -> t.List[exp.Expression]: 2845 return apply_index_offset( 2846 expression.this, 2847 expression.expressions, 2848 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2849 dialect=self.dialect, 2850 ) 2851 2852 def bracket_sql(self, expression: exp.Bracket) -> str: 2853 expressions = self.bracket_offset_expressions(expression) 2854 expressions_sql = ", ".join(self.sql(e) for e in expressions) 2855 return f"{self.sql(expression, 'this')}[{expressions_sql}]" 2856 2857 def all_sql(self, expression: exp.All) -> str: 2858 return f"ALL {self.wrap(expression)}" 2859 2860 def any_sql(self, expression: exp.Any) -> str: 2861 this = self.sql(expression, "this") 2862 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2863 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2864 this = self.wrap(this) 2865 return f"ANY{this}" 2866 return f"ANY {this}" 2867 2868 def exists_sql(self, expression: exp.Exists) -> str: 2869 return f"EXISTS{self.wrap(expression)}" 2870 2871 def case_sql(self, expression: exp.Case) -> str: 2872 this = self.sql(expression, "this") 2873 statements = [f"CASE {this}" if this else "CASE"] 2874 2875 for e in expression.args["ifs"]: 2876 statements.append(f"WHEN {self.sql(e, 'this')}") 2877 statements.append(f"THEN {self.sql(e, 'true')}") 2878 2879 default = self.sql(expression, "default") 2880 2881 if default: 2882 statements.append(f"ELSE {default}") 2883 2884 statements.append("END") 2885 2886 if self.pretty and self.too_wide(statements): 2887 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2888 2889 return " ".join(statements) 2890 2891 def constraint_sql(self, expression: exp.Constraint) -> str: 2892 this = self.sql(expression, "this") 2893 expressions = self.expressions(expression, flat=True) 2894 return f"CONSTRAINT {this} {expressions}" 2895 2896 def nextvaluefor_sql(self, expression: exp.NextValueFor) -> str: 2897 order = expression.args.get("order") 2898 order = f" OVER ({self.order_sql(order, flat=True)})" if order else "" 2899 return f"NEXT VALUE FOR {self.sql(expression, 'this')}{order}" 2900 2901 def extract_sql(self, expression: exp.Extract) -> str: 2902 this = self.sql(expression, "this") if self.EXTRACT_ALLOWS_QUOTES else expression.this.name 2903 expression_sql = self.sql(expression, "expression") 2904 return f"EXTRACT({this} FROM {expression_sql})" 2905 2906 def trim_sql(self, expression: exp.Trim) -> str: 2907 trim_type = self.sql(expression, "position") 2908 2909 if trim_type == "LEADING": 2910 func_name = "LTRIM" 2911 elif trim_type == "TRAILING": 2912 func_name = "RTRIM" 2913 else: 2914 func_name = "TRIM" 2915 2916 return self.func(func_name, expression.this, expression.expression) 2917 2918 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2919 args = expression.expressions 2920 if isinstance(expression, exp.ConcatWs): 2921 args = args[1:] # Skip the delimiter 2922 2923 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2924 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2925 2926 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2927 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2928 2929 return args 2930 2931 def concat_sql(self, expression: exp.Concat) -> str: 2932 expressions = self.convert_concat_args(expression) 2933 2934 # Some dialects don't allow a single-argument CONCAT call 2935 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2936 return self.sql(expressions[0]) 2937 2938 return self.func("CONCAT", *expressions) 2939 2940 def concatws_sql(self, expression: exp.ConcatWs) -> str: 2941 return self.func( 2942 "CONCAT_WS", seq_get(expression.expressions, 0), *self.convert_concat_args(expression) 2943 ) 2944 2945 def check_sql(self, expression: exp.Check) -> str: 2946 this = self.sql(expression, key="this") 2947 return f"CHECK ({this})" 2948 2949 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2950 expressions = self.expressions(expression, flat=True) 2951 expressions = f" ({expressions})" if expressions else "" 2952 reference = self.sql(expression, "reference") 2953 reference = f" {reference}" if reference else "" 2954 delete = self.sql(expression, "delete") 2955 delete = f" ON DELETE {delete}" if delete else "" 2956 update = self.sql(expression, "update") 2957 update = f" ON UPDATE {update}" if update else "" 2958 options = self.expressions(expression, key="options", flat=True, sep=" ") 2959 options = f" {options}" if options else "" 2960 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}" 2961 2962 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2963 expressions = self.expressions(expression, flat=True) 2964 options = self.expressions(expression, key="options", flat=True, sep=" ") 2965 options = f" {options}" if options else "" 2966 return f"PRIMARY KEY ({expressions}){options}" 2967 2968 def if_sql(self, expression: exp.If) -> str: 2969 return self.case_sql(exp.Case(ifs=[expression], default=expression.args.get("false"))) 2970 2971 def matchagainst_sql(self, expression: exp.MatchAgainst) -> str: 2972 modifier = expression.args.get("modifier") 2973 modifier = f" {modifier}" if modifier else "" 2974 return f"{self.func('MATCH', *expression.expressions)} AGAINST({self.sql(expression, 'this')}{modifier})" 2975 2976 def jsonkeyvalue_sql(self, expression: exp.JSONKeyValue) -> str: 2977 return f"{self.sql(expression, 'this')}{self.JSON_KEY_VALUE_PAIR_SEP} {self.sql(expression, 'expression')}" 2978 2979 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2980 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2981 2982 if expression.args.get("escape"): 2983 path = self.escape_str(path) 2984 2985 if self.QUOTE_JSON_PATH: 2986 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2987 2988 return path 2989 2990 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2991 if isinstance(expression, exp.JSONPathPart): 2992 transform = self.TRANSFORMS.get(expression.__class__) 2993 if not callable(transform): 2994 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2995 return "" 2996 2997 return transform(self, expression) 2998 2999 if isinstance(expression, int): 3000 return str(expression) 3001 3002 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3003 escaped = expression.replace("'", "\\'") 3004 escaped = f"\\'{expression}\\'" 3005 else: 3006 escaped = expression.replace('"', '\\"') 3007 escaped = f'"{escaped}"' 3008 3009 return escaped 3010 3011 def formatjson_sql(self, expression: exp.FormatJson) -> str: 3012 return f"{self.sql(expression, 'this')} FORMAT JSON" 3013 3014 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3015 null_handling = expression.args.get("null_handling") 3016 null_handling = f" {null_handling}" if null_handling else "" 3017 3018 unique_keys = expression.args.get("unique_keys") 3019 if unique_keys is not None: 3020 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3021 else: 3022 unique_keys = "" 3023 3024 return_type = self.sql(expression, "return_type") 3025 return_type = f" RETURNING {return_type}" if return_type else "" 3026 encoding = self.sql(expression, "encoding") 3027 encoding = f" ENCODING {encoding}" if encoding else "" 3028 3029 return self.func( 3030 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3031 *expression.expressions, 3032 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3033 ) 3034 3035 def jsonobjectagg_sql(self, expression: exp.JSONObjectAgg) -> str: 3036 return self.jsonobject_sql(expression) 3037 3038 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3039 null_handling = expression.args.get("null_handling") 3040 null_handling = f" {null_handling}" if null_handling else "" 3041 return_type = self.sql(expression, "return_type") 3042 return_type = f" RETURNING {return_type}" if return_type else "" 3043 strict = " STRICT" if expression.args.get("strict") else "" 3044 return self.func( 3045 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3046 ) 3047 3048 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3049 this = self.sql(expression, "this") 3050 order = self.sql(expression, "order") 3051 null_handling = expression.args.get("null_handling") 3052 null_handling = f" {null_handling}" if null_handling else "" 3053 return_type = self.sql(expression, "return_type") 3054 return_type = f" RETURNING {return_type}" if return_type else "" 3055 strict = " STRICT" if expression.args.get("strict") else "" 3056 return self.func( 3057 "JSON_ARRAYAGG", 3058 this, 3059 suffix=f"{order}{null_handling}{return_type}{strict})", 3060 ) 3061 3062 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3063 path = self.sql(expression, "path") 3064 path = f" PATH {path}" if path else "" 3065 nested_schema = self.sql(expression, "nested_schema") 3066 3067 if nested_schema: 3068 return f"NESTED{path} {nested_schema}" 3069 3070 this = self.sql(expression, "this") 3071 kind = self.sql(expression, "kind") 3072 kind = f" {kind}" if kind else "" 3073 return f"{this}{kind}{path}" 3074 3075 def jsonschema_sql(self, expression: exp.JSONSchema) -> str: 3076 return self.func("COLUMNS", *expression.expressions) 3077 3078 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3079 this = self.sql(expression, "this") 3080 path = self.sql(expression, "path") 3081 path = f", {path}" if path else "" 3082 error_handling = expression.args.get("error_handling") 3083 error_handling = f" {error_handling}" if error_handling else "" 3084 empty_handling = expression.args.get("empty_handling") 3085 empty_handling = f" {empty_handling}" if empty_handling else "" 3086 schema = self.sql(expression, "schema") 3087 return self.func( 3088 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3089 ) 3090 3091 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3092 this = self.sql(expression, "this") 3093 kind = self.sql(expression, "kind") 3094 path = self.sql(expression, "path") 3095 path = f" {path}" if path else "" 3096 as_json = " AS JSON" if expression.args.get("as_json") else "" 3097 return f"{this} {kind}{path}{as_json}" 3098 3099 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3100 this = self.sql(expression, "this") 3101 path = self.sql(expression, "path") 3102 path = f", {path}" if path else "" 3103 expressions = self.expressions(expression) 3104 with_ = ( 3105 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3106 if expressions 3107 else "" 3108 ) 3109 return f"OPENJSON({this}{path}){with_}" 3110 3111 def in_sql(self, expression: exp.In) -> str: 3112 query = expression.args.get("query") 3113 unnest = expression.args.get("unnest") 3114 field = expression.args.get("field") 3115 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3116 3117 if query: 3118 in_sql = self.sql(query) 3119 elif unnest: 3120 in_sql = self.in_unnest_op(unnest) 3121 elif field: 3122 in_sql = self.sql(field) 3123 else: 3124 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3125 3126 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}" 3127 3128 def in_unnest_op(self, unnest: exp.Unnest) -> str: 3129 return f"(SELECT {self.sql(unnest)})" 3130 3131 def interval_sql(self, expression: exp.Interval) -> str: 3132 unit = self.sql(expression, "unit") 3133 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3134 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3135 unit = f" {unit}" if unit else "" 3136 3137 if self.SINGLE_STRING_INTERVAL: 3138 this = expression.this.name if expression.this else "" 3139 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3140 3141 this = self.sql(expression, "this") 3142 if this: 3143 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3144 this = f" {this}" if unwrapped else f" ({this})" 3145 3146 return f"INTERVAL{this}{unit}" 3147 3148 def return_sql(self, expression: exp.Return) -> str: 3149 return f"RETURN {self.sql(expression, 'this')}" 3150 3151 def reference_sql(self, expression: exp.Reference) -> str: 3152 this = self.sql(expression, "this") 3153 expressions = self.expressions(expression, flat=True) 3154 expressions = f"({expressions})" if expressions else "" 3155 options = self.expressions(expression, key="options", flat=True, sep=" ") 3156 options = f" {options}" if options else "" 3157 return f"REFERENCES {this}{expressions}{options}" 3158 3159 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3160 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3161 parent = expression.parent 3162 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3163 return self.func( 3164 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3165 ) 3166 3167 def paren_sql(self, expression: exp.Paren) -> str: 3168 sql = self.seg(self.indent(self.sql(expression, "this")), sep="") 3169 return f"({sql}{self.seg(')', sep='')}" 3170 3171 def neg_sql(self, expression: exp.Neg) -> str: 3172 # This makes sure we don't convert "- - 5" to "--5", which is a comment 3173 this_sql = self.sql(expression, "this") 3174 sep = " " if this_sql[0] == "-" else "" 3175 return f"-{sep}{this_sql}" 3176 3177 def not_sql(self, expression: exp.Not) -> str: 3178 return f"NOT {self.sql(expression, 'this')}" 3179 3180 def alias_sql(self, expression: exp.Alias) -> str: 3181 alias = self.sql(expression, "alias") 3182 alias = f" AS {alias}" if alias else "" 3183 return f"{self.sql(expression, 'this')}{alias}" 3184 3185 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3186 alias = expression.args["alias"] 3187 3188 parent = expression.parent 3189 pivot = parent and parent.parent 3190 3191 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3192 identifier_alias = isinstance(alias, exp.Identifier) 3193 literal_alias = isinstance(alias, exp.Literal) 3194 3195 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3196 alias.replace(exp.Literal.string(alias.output_name)) 3197 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3198 alias.replace(exp.to_identifier(alias.output_name)) 3199 3200 return self.alias_sql(expression) 3201 3202 def aliases_sql(self, expression: exp.Aliases) -> str: 3203 return f"{self.sql(expression, 'this')} AS ({self.expressions(expression, flat=True)})" 3204 3205 def atindex_sql(self, expression: exp.AtTimeZone) -> str: 3206 this = self.sql(expression, "this") 3207 index = self.sql(expression, "expression") 3208 return f"{this} AT {index}" 3209 3210 def attimezone_sql(self, expression: exp.AtTimeZone) -> str: 3211 this = self.sql(expression, "this") 3212 zone = self.sql(expression, "zone") 3213 return f"{this} AT TIME ZONE {zone}" 3214 3215 def fromtimezone_sql(self, expression: exp.FromTimeZone) -> str: 3216 this = self.sql(expression, "this") 3217 zone = self.sql(expression, "zone") 3218 return f"{this} AT TIME ZONE {zone} AT TIME ZONE 'UTC'" 3219 3220 def add_sql(self, expression: exp.Add) -> str: 3221 return self.binary(expression, "+") 3222 3223 def and_sql( 3224 self, expression: exp.And, stack: t.Optional[t.List[str | exp.Expression]] = None 3225 ) -> str: 3226 return self.connector_sql(expression, "AND", stack) 3227 3228 def or_sql( 3229 self, expression: exp.Or, stack: t.Optional[t.List[str | exp.Expression]] = None 3230 ) -> str: 3231 return self.connector_sql(expression, "OR", stack) 3232 3233 def xor_sql( 3234 self, expression: exp.Xor, stack: t.Optional[t.List[str | exp.Expression]] = None 3235 ) -> str: 3236 return self.connector_sql(expression, "XOR", stack) 3237 3238 def connector_sql( 3239 self, 3240 expression: exp.Connector, 3241 op: str, 3242 stack: t.Optional[t.List[str | exp.Expression]] = None, 3243 ) -> str: 3244 if stack is not None: 3245 if expression.expressions: 3246 stack.append(self.expressions(expression, sep=f" {op} ")) 3247 else: 3248 stack.append(expression.right) 3249 if expression.comments and self.comments: 3250 for comment in expression.comments: 3251 if comment: 3252 op += f" /*{self.pad_comment(comment)}*/" 3253 stack.extend((op, expression.left)) 3254 return op 3255 3256 stack = [expression] 3257 sqls: t.List[str] = [] 3258 ops = set() 3259 3260 while stack: 3261 node = stack.pop() 3262 if isinstance(node, exp.Connector): 3263 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3264 else: 3265 sql = self.sql(node) 3266 if sqls and sqls[-1] in ops: 3267 sqls[-1] += f" {sql}" 3268 else: 3269 sqls.append(sql) 3270 3271 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3272 return sep.join(sqls) 3273 3274 def bitwiseand_sql(self, expression: exp.BitwiseAnd) -> str: 3275 return self.binary(expression, "&") 3276 3277 def bitwiseleftshift_sql(self, expression: exp.BitwiseLeftShift) -> str: 3278 return self.binary(expression, "<<") 3279 3280 def bitwisenot_sql(self, expression: exp.BitwiseNot) -> str: 3281 return f"~{self.sql(expression, 'this')}" 3282 3283 def bitwiseor_sql(self, expression: exp.BitwiseOr) -> str: 3284 return self.binary(expression, "|") 3285 3286 def bitwiserightshift_sql(self, expression: exp.BitwiseRightShift) -> str: 3287 return self.binary(expression, ">>") 3288 3289 def bitwisexor_sql(self, expression: exp.BitwiseXor) -> str: 3290 return self.binary(expression, "^") 3291 3292 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3293 format_sql = self.sql(expression, "format") 3294 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3295 to_sql = self.sql(expression, "to") 3296 to_sql = f" {to_sql}" if to_sql else "" 3297 action = self.sql(expression, "action") 3298 action = f" {action}" if action else "" 3299 default = self.sql(expression, "default") 3300 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3301 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})" 3302 3303 def currentdate_sql(self, expression: exp.CurrentDate) -> str: 3304 zone = self.sql(expression, "this") 3305 return f"CURRENT_DATE({zone})" if zone else "CURRENT_DATE" 3306 3307 def collate_sql(self, expression: exp.Collate) -> str: 3308 if self.COLLATE_IS_FUNC: 3309 return self.function_fallback_sql(expression) 3310 return self.binary(expression, "COLLATE") 3311 3312 def command_sql(self, expression: exp.Command) -> str: 3313 return f"{self.sql(expression, 'this')} {expression.text('expression').strip()}" 3314 3315 def comment_sql(self, expression: exp.Comment) -> str: 3316 this = self.sql(expression, "this") 3317 kind = expression.args["kind"] 3318 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3319 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3320 expression_sql = self.sql(expression, "expression") 3321 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}" 3322 3323 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3324 this = self.sql(expression, "this") 3325 delete = " DELETE" if expression.args.get("delete") else "" 3326 recompress = self.sql(expression, "recompress") 3327 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3328 to_disk = self.sql(expression, "to_disk") 3329 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3330 to_volume = self.sql(expression, "to_volume") 3331 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3332 return f"{this}{delete}{recompress}{to_disk}{to_volume}" 3333 3334 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3335 where = self.sql(expression, "where") 3336 group = self.sql(expression, "group") 3337 aggregates = self.expressions(expression, key="aggregates") 3338 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3339 3340 if not (where or group or aggregates) and len(expression.expressions) == 1: 3341 return f"TTL {self.expressions(expression, flat=True)}" 3342 3343 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}" 3344 3345 def transaction_sql(self, expression: exp.Transaction) -> str: 3346 return "BEGIN" 3347 3348 def commit_sql(self, expression: exp.Commit) -> str: 3349 chain = expression.args.get("chain") 3350 if chain is not None: 3351 chain = " AND CHAIN" if chain else " AND NO CHAIN" 3352 3353 return f"COMMIT{chain or ''}" 3354 3355 def rollback_sql(self, expression: exp.Rollback) -> str: 3356 savepoint = expression.args.get("savepoint") 3357 savepoint = f" TO {savepoint}" if savepoint else "" 3358 return f"ROLLBACK{savepoint}" 3359 3360 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3361 this = self.sql(expression, "this") 3362 3363 dtype = self.sql(expression, "dtype") 3364 if dtype: 3365 collate = self.sql(expression, "collate") 3366 collate = f" COLLATE {collate}" if collate else "" 3367 using = self.sql(expression, "using") 3368 using = f" USING {using}" if using else "" 3369 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3370 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3371 3372 default = self.sql(expression, "default") 3373 if default: 3374 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3375 3376 comment = self.sql(expression, "comment") 3377 if comment: 3378 return f"ALTER COLUMN {this} COMMENT {comment}" 3379 3380 visible = expression.args.get("visible") 3381 if visible: 3382 return f"ALTER COLUMN {this} SET {visible}" 3383 3384 allow_null = expression.args.get("allow_null") 3385 drop = expression.args.get("drop") 3386 3387 if not drop and not allow_null: 3388 self.unsupported("Unsupported ALTER COLUMN syntax") 3389 3390 if allow_null is not None: 3391 keyword = "DROP" if drop else "SET" 3392 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3393 3394 return f"ALTER COLUMN {this} DROP DEFAULT" 3395 3396 def alterindex_sql(self, expression: exp.AlterIndex) -> str: 3397 this = self.sql(expression, "this") 3398 3399 visible = expression.args.get("visible") 3400 visible_sql = "VISIBLE" if visible else "INVISIBLE" 3401 3402 return f"ALTER INDEX {this} {visible_sql}" 3403 3404 def alterdiststyle_sql(self, expression: exp.AlterDistStyle) -> str: 3405 this = self.sql(expression, "this") 3406 if not isinstance(expression.this, exp.Var): 3407 this = f"KEY DISTKEY {this}" 3408 return f"ALTER DISTSTYLE {this}" 3409 3410 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3411 compound = " COMPOUND" if expression.args.get("compound") else "" 3412 this = self.sql(expression, "this") 3413 expressions = self.expressions(expression, flat=True) 3414 expressions = f"({expressions})" if expressions else "" 3415 return f"ALTER{compound} SORTKEY {this or expressions}" 3416 3417 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3418 if not self.RENAME_TABLE_WITH_DB: 3419 # Remove db from tables 3420 expression = expression.transform( 3421 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3422 ).assert_is(exp.AlterRename) 3423 this = self.sql(expression, "this") 3424 return f"RENAME TO {this}" 3425 3426 def renamecolumn_sql(self, expression: exp.RenameColumn) -> str: 3427 exists = " IF EXISTS" if expression.args.get("exists") else "" 3428 old_column = self.sql(expression, "this") 3429 new_column = self.sql(expression, "to") 3430 return f"RENAME COLUMN{exists} {old_column} TO {new_column}" 3431 3432 def alterset_sql(self, expression: exp.AlterSet) -> str: 3433 exprs = self.expressions(expression, flat=True) 3434 return f"SET {exprs}" 3435 3436 def alter_sql(self, expression: exp.Alter) -> str: 3437 actions = expression.args["actions"] 3438 3439 if isinstance(actions[0], exp.ColumnDef): 3440 actions = self.add_column_sql(expression) 3441 elif isinstance(actions[0], exp.Schema): 3442 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3443 elif isinstance(actions[0], exp.Delete): 3444 actions = self.expressions(expression, key="actions", flat=True) 3445 elif isinstance(actions[0], exp.Query): 3446 actions = "AS " + self.expressions(expression, key="actions") 3447 else: 3448 actions = self.expressions(expression, key="actions", flat=True) 3449 3450 exists = " IF EXISTS" if expression.args.get("exists") else "" 3451 on_cluster = self.sql(expression, "cluster") 3452 on_cluster = f" {on_cluster}" if on_cluster else "" 3453 only = " ONLY" if expression.args.get("only") else "" 3454 options = self.expressions(expression, key="options") 3455 options = f", {options}" if options else "" 3456 kind = self.sql(expression, "kind") 3457 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3458 3459 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}" 3460 3461 def add_column_sql(self, expression: exp.Alter) -> str: 3462 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3463 return self.expressions( 3464 expression, 3465 key="actions", 3466 prefix="ADD COLUMN ", 3467 skip_first=True, 3468 ) 3469 return f"ADD {self.expressions(expression, key='actions', flat=True)}" 3470 3471 def droppartition_sql(self, expression: exp.DropPartition) -> str: 3472 expressions = self.expressions(expression) 3473 exists = " IF EXISTS " if expression.args.get("exists") else " " 3474 return f"DROP{exists}{expressions}" 3475 3476 def addconstraint_sql(self, expression: exp.AddConstraint) -> str: 3477 return f"ADD {self.expressions(expression)}" 3478 3479 def distinct_sql(self, expression: exp.Distinct) -> str: 3480 this = self.expressions(expression, flat=True) 3481 3482 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3483 case = exp.case() 3484 for arg in expression.expressions: 3485 case = case.when(arg.is_(exp.null()), exp.null()) 3486 this = self.sql(case.else_(f"({this})")) 3487 3488 this = f" {this}" if this else "" 3489 3490 on = self.sql(expression, "on") 3491 on = f" ON {on}" if on else "" 3492 return f"DISTINCT{this}{on}" 3493 3494 def ignorenulls_sql(self, expression: exp.IgnoreNulls) -> str: 3495 return self._embed_ignore_nulls(expression, "IGNORE NULLS") 3496 3497 def respectnulls_sql(self, expression: exp.RespectNulls) -> str: 3498 return self._embed_ignore_nulls(expression, "RESPECT NULLS") 3499 3500 def havingmax_sql(self, expression: exp.HavingMax) -> str: 3501 this_sql = self.sql(expression, "this") 3502 expression_sql = self.sql(expression, "expression") 3503 kind = "MAX" if expression.args.get("max") else "MIN" 3504 return f"{this_sql} HAVING {kind} {expression_sql}" 3505 3506 def intdiv_sql(self, expression: exp.IntDiv) -> str: 3507 return self.sql( 3508 exp.Cast( 3509 this=exp.Div(this=expression.this, expression=expression.expression), 3510 to=exp.DataType(this=exp.DataType.Type.INT), 3511 ) 3512 ) 3513 3514 def dpipe_sql(self, expression: exp.DPipe) -> str: 3515 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 3516 return self.func( 3517 "CONCAT", *(exp.cast(e, exp.DataType.Type.TEXT) for e in expression.flatten()) 3518 ) 3519 return self.binary(expression, "||") 3520 3521 def div_sql(self, expression: exp.Div) -> str: 3522 l, r = expression.left, expression.right 3523 3524 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3525 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3526 3527 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3528 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3529 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3530 3531 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3532 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3533 return self.sql( 3534 exp.cast( 3535 l / r, 3536 to=exp.DataType.Type.BIGINT, 3537 ) 3538 ) 3539 3540 return self.binary(expression, "/") 3541 3542 def safedivide_sql(self, expression: exp.SafeDivide) -> str: 3543 n = exp._wrap(expression.this, exp.Binary) 3544 d = exp._wrap(expression.expression, exp.Binary) 3545 return self.sql(exp.If(this=d.neq(0), true=n / d, false=exp.Null())) 3546 3547 def overlaps_sql(self, expression: exp.Overlaps) -> str: 3548 return self.binary(expression, "OVERLAPS") 3549 3550 def distance_sql(self, expression: exp.Distance) -> str: 3551 return self.binary(expression, "<->") 3552 3553 def dot_sql(self, expression: exp.Dot) -> str: 3554 return f"{self.sql(expression, 'this')}.{self.sql(expression, 'expression')}" 3555 3556 def eq_sql(self, expression: exp.EQ) -> str: 3557 return self.binary(expression, "=") 3558 3559 def propertyeq_sql(self, expression: exp.PropertyEQ) -> str: 3560 return self.binary(expression, ":=") 3561 3562 def escape_sql(self, expression: exp.Escape) -> str: 3563 return self.binary(expression, "ESCAPE") 3564 3565 def glob_sql(self, expression: exp.Glob) -> str: 3566 return self.binary(expression, "GLOB") 3567 3568 def gt_sql(self, expression: exp.GT) -> str: 3569 return self.binary(expression, ">") 3570 3571 def gte_sql(self, expression: exp.GTE) -> str: 3572 return self.binary(expression, ">=") 3573 3574 def ilike_sql(self, expression: exp.ILike) -> str: 3575 return self.binary(expression, "ILIKE") 3576 3577 def ilikeany_sql(self, expression: exp.ILikeAny) -> str: 3578 return self.binary(expression, "ILIKE ANY") 3579 3580 def is_sql(self, expression: exp.Is) -> str: 3581 if not self.IS_BOOL_ALLOWED and isinstance(expression.expression, exp.Boolean): 3582 return self.sql( 3583 expression.this if expression.expression.this else exp.not_(expression.this) 3584 ) 3585 return self.binary(expression, "IS") 3586 3587 def like_sql(self, expression: exp.Like) -> str: 3588 return self.binary(expression, "LIKE") 3589 3590 def likeany_sql(self, expression: exp.LikeAny) -> str: 3591 return self.binary(expression, "LIKE ANY") 3592 3593 def similarto_sql(self, expression: exp.SimilarTo) -> str: 3594 return self.binary(expression, "SIMILAR TO") 3595 3596 def lt_sql(self, expression: exp.LT) -> str: 3597 return self.binary(expression, "<") 3598 3599 def lte_sql(self, expression: exp.LTE) -> str: 3600 return self.binary(expression, "<=") 3601 3602 def mod_sql(self, expression: exp.Mod) -> str: 3603 return self.binary(expression, "%") 3604 3605 def mul_sql(self, expression: exp.Mul) -> str: 3606 return self.binary(expression, "*") 3607 3608 def neq_sql(self, expression: exp.NEQ) -> str: 3609 return self.binary(expression, "<>") 3610 3611 def nullsafeeq_sql(self, expression: exp.NullSafeEQ) -> str: 3612 return self.binary(expression, "IS NOT DISTINCT FROM") 3613 3614 def nullsafeneq_sql(self, expression: exp.NullSafeNEQ) -> str: 3615 return self.binary(expression, "IS DISTINCT FROM") 3616 3617 def slice_sql(self, expression: exp.Slice) -> str: 3618 return self.binary(expression, ":") 3619 3620 def sub_sql(self, expression: exp.Sub) -> str: 3621 return self.binary(expression, "-") 3622 3623 def trycast_sql(self, expression: exp.TryCast) -> str: 3624 return self.cast_sql(expression, safe_prefix="TRY_") 3625 3626 def jsoncast_sql(self, expression: exp.JSONCast) -> str: 3627 return self.cast_sql(expression) 3628 3629 def try_sql(self, expression: exp.Try) -> str: 3630 if not self.TRY_SUPPORTED: 3631 self.unsupported("Unsupported TRY function") 3632 return self.sql(expression, "this") 3633 3634 return self.func("TRY", expression.this) 3635 3636 def log_sql(self, expression: exp.Log) -> str: 3637 this = expression.this 3638 expr = expression.expression 3639 3640 if self.dialect.LOG_BASE_FIRST is False: 3641 this, expr = expr, this 3642 elif self.dialect.LOG_BASE_FIRST is None and expr: 3643 if this.name in ("2", "10"): 3644 return self.func(f"LOG{this.name}", expr) 3645 3646 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3647 3648 return self.func("LOG", this, expr) 3649 3650 def use_sql(self, expression: exp.Use) -> str: 3651 kind = self.sql(expression, "kind") 3652 kind = f" {kind}" if kind else "" 3653 this = self.sql(expression, "this") or self.expressions(expression, flat=True) 3654 this = f" {this}" if this else "" 3655 return f"USE{kind}{this}" 3656 3657 def binary(self, expression: exp.Binary, op: str) -> str: 3658 sqls: t.List[str] = [] 3659 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3660 binary_type = type(expression) 3661 3662 while stack: 3663 node = stack.pop() 3664 3665 if type(node) is binary_type: 3666 op_func = node.args.get("operator") 3667 if op_func: 3668 op = f"OPERATOR({self.sql(op_func)})" 3669 3670 stack.append(node.right) 3671 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3672 stack.append(node.left) 3673 else: 3674 sqls.append(self.sql(node)) 3675 3676 return "".join(sqls) 3677 3678 def ceil_floor(self, expression: exp.Ceil | exp.Floor) -> str: 3679 to_clause = self.sql(expression, "to") 3680 if to_clause: 3681 return f"{expression.sql_name()}({self.sql(expression, 'this')} TO {to_clause})" 3682 3683 return self.function_fallback_sql(expression) 3684 3685 def function_fallback_sql(self, expression: exp.Func) -> str: 3686 args = [] 3687 3688 for key in expression.arg_types: 3689 arg_value = expression.args.get(key) 3690 3691 if isinstance(arg_value, list): 3692 for value in arg_value: 3693 args.append(value) 3694 elif arg_value is not None: 3695 args.append(arg_value) 3696 3697 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3698 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3699 else: 3700 name = expression.sql_name() 3701 3702 return self.func(name, *args) 3703 3704 def func( 3705 self, 3706 name: str, 3707 *args: t.Optional[exp.Expression | str], 3708 prefix: str = "(", 3709 suffix: str = ")", 3710 normalize: bool = True, 3711 ) -> str: 3712 name = self.normalize_func(name) if normalize else name 3713 return f"{name}{prefix}{self.format_args(*args)}{suffix}" 3714 3715 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3716 arg_sqls = tuple( 3717 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3718 ) 3719 if self.pretty and self.too_wide(arg_sqls): 3720 return self.indent( 3721 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3722 ) 3723 return sep.join(arg_sqls) 3724 3725 def too_wide(self, args: t.Iterable) -> bool: 3726 return sum(len(arg) for arg in args) > self.max_text_width 3727 3728 def format_time( 3729 self, 3730 expression: exp.Expression, 3731 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3732 inverse_time_trie: t.Optional[t.Dict] = None, 3733 ) -> t.Optional[str]: 3734 return format_time( 3735 self.sql(expression, "format"), 3736 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3737 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3738 ) 3739 3740 def expressions( 3741 self, 3742 expression: t.Optional[exp.Expression] = None, 3743 key: t.Optional[str] = None, 3744 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3745 flat: bool = False, 3746 indent: bool = True, 3747 skip_first: bool = False, 3748 skip_last: bool = False, 3749 sep: str = ", ", 3750 prefix: str = "", 3751 dynamic: bool = False, 3752 new_line: bool = False, 3753 ) -> str: 3754 expressions = expression.args.get(key or "expressions") if expression else sqls 3755 3756 if not expressions: 3757 return "" 3758 3759 if flat: 3760 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3761 3762 num_sqls = len(expressions) 3763 result_sqls = [] 3764 3765 for i, e in enumerate(expressions): 3766 sql = self.sql(e, comment=False) 3767 if not sql: 3768 continue 3769 3770 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3771 3772 if self.pretty: 3773 if self.leading_comma: 3774 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3775 else: 3776 result_sqls.append( 3777 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3778 ) 3779 else: 3780 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3781 3782 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3783 if new_line: 3784 result_sqls.insert(0, "") 3785 result_sqls.append("") 3786 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3787 else: 3788 result_sql = "".join(result_sqls) 3789 3790 return ( 3791 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3792 if indent 3793 else result_sql 3794 ) 3795 3796 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3797 flat = flat or isinstance(expression.parent, exp.Properties) 3798 expressions_sql = self.expressions(expression, flat=flat) 3799 if flat: 3800 return f"{op} {expressions_sql}" 3801 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}" 3802 3803 def naked_property(self, expression: exp.Property) -> str: 3804 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3805 if not property_name: 3806 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3807 return f"{property_name} {self.sql(expression, 'this')}" 3808 3809 def tag_sql(self, expression: exp.Tag) -> str: 3810 return f"{expression.args.get('prefix')}{self.sql(expression.this)}{expression.args.get('postfix')}" 3811 3812 def token_sql(self, token_type: TokenType) -> str: 3813 return self.TOKEN_MAPPING.get(token_type, token_type.name) 3814 3815 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3816 this = self.sql(expression, "this") 3817 expressions = self.no_identify(self.expressions, expression) 3818 expressions = ( 3819 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3820 ) 3821 return f"{this}{expressions}" if expressions.strip() != "" else this 3822 3823 def joinhint_sql(self, expression: exp.JoinHint) -> str: 3824 this = self.sql(expression, "this") 3825 expressions = self.expressions(expression, flat=True) 3826 return f"{this}({expressions})" 3827 3828 def kwarg_sql(self, expression: exp.Kwarg) -> str: 3829 return self.binary(expression, "=>") 3830 3831 def when_sql(self, expression: exp.When) -> str: 3832 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3833 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3834 condition = self.sql(expression, "condition") 3835 condition = f" AND {condition}" if condition else "" 3836 3837 then_expression = expression.args.get("then") 3838 if isinstance(then_expression, exp.Insert): 3839 this = self.sql(then_expression, "this") 3840 this = f"INSERT {this}" if this else "INSERT" 3841 then = self.sql(then_expression, "expression") 3842 then = f"{this} VALUES {then}" if then else this 3843 elif isinstance(then_expression, exp.Update): 3844 if isinstance(then_expression.args.get("expressions"), exp.Star): 3845 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3846 else: 3847 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3848 else: 3849 then = self.sql(then_expression) 3850 return f"WHEN {matched}{source}{condition} THEN {then}" 3851 3852 def whens_sql(self, expression: exp.Whens) -> str: 3853 return self.expressions(expression, sep=" ", indent=False) 3854 3855 def merge_sql(self, expression: exp.Merge) -> str: 3856 table = expression.this 3857 table_alias = "" 3858 3859 hints = table.args.get("hints") 3860 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3861 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3862 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3863 3864 this = self.sql(table) 3865 using = f"USING {self.sql(expression, 'using')}" 3866 on = f"ON {self.sql(expression, 'on')}" 3867 whens = self.sql(expression, "whens") 3868 3869 returning = self.sql(expression, "returning") 3870 if returning: 3871 whens = f"{whens}{returning}" 3872 3873 sep = self.sep() 3874 3875 return self.prepend_ctes( 3876 expression, 3877 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3878 ) 3879 3880 @unsupported_args("format") 3881 def tochar_sql(self, expression: exp.ToChar) -> str: 3882 return self.sql(exp.cast(expression.this, exp.DataType.Type.TEXT)) 3883 3884 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3885 if not self.SUPPORTS_TO_NUMBER: 3886 self.unsupported("Unsupported TO_NUMBER function") 3887 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3888 3889 fmt = expression.args.get("format") 3890 if not fmt: 3891 self.unsupported("Conversion format is required for TO_NUMBER") 3892 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3893 3894 return self.func("TO_NUMBER", expression.this, fmt) 3895 3896 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3897 this = self.sql(expression, "this") 3898 kind = self.sql(expression, "kind") 3899 settings_sql = self.expressions(expression, key="settings", sep=" ") 3900 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3901 return f"{this}({kind}{args})" 3902 3903 def dictrange_sql(self, expression: exp.DictRange) -> str: 3904 this = self.sql(expression, "this") 3905 max = self.sql(expression, "max") 3906 min = self.sql(expression, "min") 3907 return f"{this}(MIN {min} MAX {max})" 3908 3909 def dictsubproperty_sql(self, expression: exp.DictSubProperty) -> str: 3910 return f"{self.sql(expression, 'this')} {self.sql(expression, 'value')}" 3911 3912 def duplicatekeyproperty_sql(self, expression: exp.DuplicateKeyProperty) -> str: 3913 return f"DUPLICATE KEY ({self.expressions(expression, flat=True)})" 3914 3915 # https://docs.starrocks.io/docs/sql-reference/sql-statements/table_bucket_part_index/CREATE_TABLE/ 3916 def uniquekeyproperty_sql(self, expression: exp.UniqueKeyProperty) -> str: 3917 return f"UNIQUE KEY ({self.expressions(expression, flat=True)})" 3918 3919 # https://docs.starrocks.io/docs/sql-reference/sql-statements/data-definition/CREATE_TABLE/#distribution_desc 3920 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3921 expressions = self.expressions(expression, flat=True) 3922 expressions = f" {self.wrap(expressions)}" if expressions else "" 3923 buckets = self.sql(expression, "buckets") 3924 kind = self.sql(expression, "kind") 3925 buckets = f" BUCKETS {buckets}" if buckets else "" 3926 order = self.sql(expression, "order") 3927 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}" 3928 3929 def oncluster_sql(self, expression: exp.OnCluster) -> str: 3930 return "" 3931 3932 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3933 expressions = self.expressions(expression, key="expressions", flat=True) 3934 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3935 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3936 buckets = self.sql(expression, "buckets") 3937 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS" 3938 3939 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3940 this = self.sql(expression, "this") 3941 having = self.sql(expression, "having") 3942 3943 if having: 3944 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3945 3946 return self.func("ANY_VALUE", this) 3947 3948 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3949 transform = self.func("TRANSFORM", *expression.expressions) 3950 row_format_before = self.sql(expression, "row_format_before") 3951 row_format_before = f" {row_format_before}" if row_format_before else "" 3952 record_writer = self.sql(expression, "record_writer") 3953 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3954 using = f" USING {self.sql(expression, 'command_script')}" 3955 schema = self.sql(expression, "schema") 3956 schema = f" AS {schema}" if schema else "" 3957 row_format_after = self.sql(expression, "row_format_after") 3958 row_format_after = f" {row_format_after}" if row_format_after else "" 3959 record_reader = self.sql(expression, "record_reader") 3960 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3961 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}" 3962 3963 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3964 key_block_size = self.sql(expression, "key_block_size") 3965 if key_block_size: 3966 return f"KEY_BLOCK_SIZE = {key_block_size}" 3967 3968 using = self.sql(expression, "using") 3969 if using: 3970 return f"USING {using}" 3971 3972 parser = self.sql(expression, "parser") 3973 if parser: 3974 return f"WITH PARSER {parser}" 3975 3976 comment = self.sql(expression, "comment") 3977 if comment: 3978 return f"COMMENT {comment}" 3979 3980 visible = expression.args.get("visible") 3981 if visible is not None: 3982 return "VISIBLE" if visible else "INVISIBLE" 3983 3984 engine_attr = self.sql(expression, "engine_attr") 3985 if engine_attr: 3986 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3987 3988 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3989 if secondary_engine_attr: 3990 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3991 3992 self.unsupported("Unsupported index constraint option.") 3993 return "" 3994 3995 def checkcolumnconstraint_sql(self, expression: exp.CheckColumnConstraint) -> str: 3996 enforced = " ENFORCED" if expression.args.get("enforced") else "" 3997 return f"CHECK ({self.sql(expression, 'this')}){enforced}" 3998 3999 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4000 kind = self.sql(expression, "kind") 4001 kind = f"{kind} INDEX" if kind else "INDEX" 4002 this = self.sql(expression, "this") 4003 this = f" {this}" if this else "" 4004 index_type = self.sql(expression, "index_type") 4005 index_type = f" USING {index_type}" if index_type else "" 4006 expressions = self.expressions(expression, flat=True) 4007 expressions = f" ({expressions})" if expressions else "" 4008 options = self.expressions(expression, key="options", sep=" ") 4009 options = f" {options}" if options else "" 4010 return f"{kind}{this}{index_type}{expressions}{options}" 4011 4012 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4013 if self.NVL2_SUPPORTED: 4014 return self.function_fallback_sql(expression) 4015 4016 case = exp.Case().when( 4017 expression.this.is_(exp.null()).not_(copy=False), 4018 expression.args["true"], 4019 copy=False, 4020 ) 4021 else_cond = expression.args.get("false") 4022 if else_cond: 4023 case.else_(else_cond, copy=False) 4024 4025 return self.sql(case) 4026 4027 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4028 this = self.sql(expression, "this") 4029 expr = self.sql(expression, "expression") 4030 iterator = self.sql(expression, "iterator") 4031 condition = self.sql(expression, "condition") 4032 condition = f" IF {condition}" if condition else "" 4033 return f"{this} FOR {expr} IN {iterator}{condition}" 4034 4035 def columnprefix_sql(self, expression: exp.ColumnPrefix) -> str: 4036 return f"{self.sql(expression, 'this')}({self.sql(expression, 'expression')})" 4037 4038 def opclass_sql(self, expression: exp.Opclass) -> str: 4039 return f"{self.sql(expression, 'this')} {self.sql(expression, 'expression')}" 4040 4041 def predict_sql(self, expression: exp.Predict) -> str: 4042 model = self.sql(expression, "this") 4043 model = f"MODEL {model}" 4044 table = self.sql(expression, "expression") 4045 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4046 parameters = self.sql(expression, "params_struct") 4047 return self.func("PREDICT", model, table, parameters or None) 4048 4049 def forin_sql(self, expression: exp.ForIn) -> str: 4050 this = self.sql(expression, "this") 4051 expression_sql = self.sql(expression, "expression") 4052 return f"FOR {this} DO {expression_sql}" 4053 4054 def refresh_sql(self, expression: exp.Refresh) -> str: 4055 this = self.sql(expression, "this") 4056 table = "" if isinstance(expression.this, exp.Literal) else "TABLE " 4057 return f"REFRESH {table}{this}" 4058 4059 def toarray_sql(self, expression: exp.ToArray) -> str: 4060 arg = expression.this 4061 if not arg.type: 4062 from sqlglot.optimizer.annotate_types import annotate_types 4063 4064 arg = annotate_types(arg, dialect=self.dialect) 4065 4066 if arg.is_type(exp.DataType.Type.ARRAY): 4067 return self.sql(arg) 4068 4069 cond_for_null = arg.is_(exp.null()) 4070 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False))) 4071 4072 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4073 this = expression.this 4074 time_format = self.format_time(expression) 4075 4076 if time_format: 4077 return self.sql( 4078 exp.cast( 4079 exp.StrToTime(this=this, format=expression.args["format"]), 4080 exp.DataType.Type.TIME, 4081 ) 4082 ) 4083 4084 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4085 return self.sql(this) 4086 4087 return self.sql(exp.cast(this, exp.DataType.Type.TIME)) 4088 4089 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4090 this = expression.this 4091 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4092 return self.sql(this) 4093 4094 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect)) 4095 4096 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4097 this = expression.this 4098 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4099 return self.sql(this) 4100 4101 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect)) 4102 4103 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4104 this = expression.this 4105 time_format = self.format_time(expression) 4106 4107 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4108 return self.sql( 4109 exp.cast( 4110 exp.StrToTime(this=this, format=expression.args["format"]), 4111 exp.DataType.Type.DATE, 4112 ) 4113 ) 4114 4115 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4116 return self.sql(this) 4117 4118 return self.sql(exp.cast(this, exp.DataType.Type.DATE)) 4119 4120 def unixdate_sql(self, expression: exp.UnixDate) -> str: 4121 return self.sql( 4122 exp.func( 4123 "DATEDIFF", 4124 expression.this, 4125 exp.cast(exp.Literal.string("1970-01-01"), exp.DataType.Type.DATE), 4126 "day", 4127 ) 4128 ) 4129 4130 def lastday_sql(self, expression: exp.LastDay) -> str: 4131 if self.LAST_DAY_SUPPORTS_DATE_PART: 4132 return self.function_fallback_sql(expression) 4133 4134 unit = expression.text("unit") 4135 if unit and unit != "MONTH": 4136 self.unsupported("Date parts are not supported in LAST_DAY.") 4137 4138 return self.func("LAST_DAY", expression.this) 4139 4140 def dateadd_sql(self, expression: exp.DateAdd) -> str: 4141 from sqlglot.dialects.dialect import unit_to_str 4142 4143 return self.func( 4144 "DATE_ADD", expression.this, expression.expression, unit_to_str(expression) 4145 ) 4146 4147 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4148 if self.CAN_IMPLEMENT_ARRAY_ANY: 4149 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4150 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4151 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4152 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4153 4154 from sqlglot.dialects import Dialect 4155 4156 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4157 if self.dialect.__class__ != Dialect: 4158 self.unsupported("ARRAY_ANY is unsupported") 4159 4160 return self.function_fallback_sql(expression) 4161 4162 def struct_sql(self, expression: exp.Struct) -> str: 4163 expression.set( 4164 "expressions", 4165 [ 4166 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4167 if isinstance(e, exp.PropertyEQ) 4168 else e 4169 for e in expression.expressions 4170 ], 4171 ) 4172 4173 return self.function_fallback_sql(expression) 4174 4175 def partitionrange_sql(self, expression: exp.PartitionRange) -> str: 4176 low = self.sql(expression, "this") 4177 high = self.sql(expression, "expression") 4178 4179 return f"{low} TO {high}" 4180 4181 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4182 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4183 tables = f" {self.expressions(expression)}" 4184 4185 exists = " IF EXISTS" if expression.args.get("exists") else "" 4186 4187 on_cluster = self.sql(expression, "cluster") 4188 on_cluster = f" {on_cluster}" if on_cluster else "" 4189 4190 identity = self.sql(expression, "identity") 4191 identity = f" {identity} IDENTITY" if identity else "" 4192 4193 option = self.sql(expression, "option") 4194 option = f" {option}" if option else "" 4195 4196 partition = self.sql(expression, "partition") 4197 partition = f" {partition}" if partition else "" 4198 4199 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}" 4200 4201 # This transpiles T-SQL's CONVERT function 4202 # https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 4203 def convert_sql(self, expression: exp.Convert) -> str: 4204 to = expression.this 4205 value = expression.expression 4206 style = expression.args.get("style") 4207 safe = expression.args.get("safe") 4208 strict = expression.args.get("strict") 4209 4210 if not to or not value: 4211 return "" 4212 4213 # Retrieve length of datatype and override to default if not specified 4214 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4215 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4216 4217 transformed: t.Optional[exp.Expression] = None 4218 cast = exp.Cast if strict else exp.TryCast 4219 4220 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4221 if isinstance(style, exp.Literal) and style.is_int: 4222 from sqlglot.dialects.tsql import TSQL 4223 4224 style_value = style.name 4225 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4226 if not converted_style: 4227 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4228 4229 fmt = exp.Literal.string(converted_style) 4230 4231 if to.this == exp.DataType.Type.DATE: 4232 transformed = exp.StrToDate(this=value, format=fmt) 4233 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4234 transformed = exp.StrToTime(this=value, format=fmt) 4235 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4236 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4237 elif to.this == exp.DataType.Type.TEXT: 4238 transformed = exp.TimeToStr(this=value, format=fmt) 4239 4240 if not transformed: 4241 transformed = cast(this=value, to=to, safe=safe) 4242 4243 return self.sql(transformed) 4244 4245 def _jsonpathkey_sql(self, expression: exp.JSONPathKey) -> str: 4246 this = expression.this 4247 if isinstance(this, exp.JSONPathWildcard): 4248 this = self.json_path_part(this) 4249 return f".{this}" if this else "" 4250 4251 if exp.SAFE_IDENTIFIER_RE.match(this): 4252 return f".{this}" 4253 4254 this = self.json_path_part(this) 4255 return ( 4256 f"[{this}]" 4257 if self._quote_json_path_key_using_brackets and self.JSON_PATH_BRACKETED_KEY_SUPPORTED 4258 else f".{this}" 4259 ) 4260 4261 def _jsonpathsubscript_sql(self, expression: exp.JSONPathSubscript) -> str: 4262 this = self.json_path_part(expression.this) 4263 return f"[{this}]" if this else "" 4264 4265 def _simplify_unless_literal(self, expression: E) -> E: 4266 if not isinstance(expression, exp.Literal): 4267 from sqlglot.optimizer.simplify import simplify 4268 4269 expression = simplify(expression, dialect=self.dialect) 4270 4271 return expression 4272 4273 def _embed_ignore_nulls(self, expression: exp.IgnoreNulls | exp.RespectNulls, text: str) -> str: 4274 this = expression.this 4275 if isinstance(this, self.RESPECT_IGNORE_NULLS_UNSUPPORTED_EXPRESSIONS): 4276 self.unsupported( 4277 f"RESPECT/IGNORE NULLS is not supported for {type(this).key} in {self.dialect.__class__.__name__}" 4278 ) 4279 return self.sql(this) 4280 4281 if self.IGNORE_NULLS_IN_FUNC and not expression.meta.get("inline"): 4282 # The first modifier here will be the one closest to the AggFunc's arg 4283 mods = sorted( 4284 expression.find_all(exp.HavingMax, exp.Order, exp.Limit), 4285 key=lambda x: 0 4286 if isinstance(x, exp.HavingMax) 4287 else (1 if isinstance(x, exp.Order) else 2), 4288 ) 4289 4290 if mods: 4291 mod = mods[0] 4292 this = expression.__class__(this=mod.this.copy()) 4293 this.meta["inline"] = True 4294 mod.this.replace(this) 4295 return self.sql(expression.this) 4296 4297 agg_func = expression.find(exp.AggFunc) 4298 4299 if agg_func: 4300 agg_func_sql = self.sql(agg_func, comment=False)[:-1] + f" {text})" 4301 return self.maybe_comment(agg_func_sql, comments=agg_func.comments) 4302 4303 return f"{self.sql(expression, 'this')} {text}" 4304 4305 def _replace_line_breaks(self, string: str) -> str: 4306 """We don't want to extra indent line breaks so we temporarily replace them with sentinels.""" 4307 if self.pretty: 4308 return string.replace("\n", self.SENTINEL_LINE_BREAK) 4309 return string 4310 4311 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4312 option = self.sql(expression, "this") 4313 4314 if expression.expressions: 4315 upper = option.upper() 4316 4317 # Snowflake FILE_FORMAT options are separated by whitespace 4318 sep = " " if upper == "FILE_FORMAT" else ", " 4319 4320 # Databricks copy/format options do not set their list of values with EQ 4321 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4322 values = self.expressions(expression, flat=True, sep=sep) 4323 return f"{option}{op}({values})" 4324 4325 value = self.sql(expression, "expression") 4326 4327 if not value: 4328 return option 4329 4330 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4331 4332 return f"{option}{op}{value}" 4333 4334 def credentials_sql(self, expression: exp.Credentials) -> str: 4335 cred_expr = expression.args.get("credentials") 4336 if isinstance(cred_expr, exp.Literal): 4337 # Redshift case: CREDENTIALS <string> 4338 credentials = self.sql(expression, "credentials") 4339 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4340 else: 4341 # Snowflake case: CREDENTIALS = (...) 4342 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4343 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4344 4345 storage = self.sql(expression, "storage") 4346 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4347 4348 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4349 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4350 4351 iam_role = self.sql(expression, "iam_role") 4352 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4353 4354 region = self.sql(expression, "region") 4355 region = f" REGION {region}" if region else "" 4356 4357 return f"{credentials}{storage}{encryption}{iam_role}{region}" 4358 4359 def copy_sql(self, expression: exp.Copy) -> str: 4360 this = self.sql(expression, "this") 4361 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4362 4363 credentials = self.sql(expression, "credentials") 4364 credentials = self.seg(credentials) if credentials else "" 4365 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4366 files = self.expressions(expression, key="files", flat=True) 4367 4368 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4369 params = self.expressions( 4370 expression, 4371 key="params", 4372 sep=sep, 4373 new_line=True, 4374 skip_last=True, 4375 skip_first=True, 4376 indent=self.COPY_PARAMS_ARE_WRAPPED, 4377 ) 4378 4379 if params: 4380 if self.COPY_PARAMS_ARE_WRAPPED: 4381 params = f" WITH ({params})" 4382 elif not self.pretty: 4383 params = f" {params}" 4384 4385 return f"COPY{this}{kind} {files}{credentials}{params}" 4386 4387 def semicolon_sql(self, expression: exp.Semicolon) -> str: 4388 return "" 4389 4390 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4391 on_sql = "ON" if expression.args.get("on") else "OFF" 4392 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4393 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4394 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4395 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4396 4397 if filter_col or retention_period: 4398 on_sql = self.func("ON", filter_col, retention_period) 4399 4400 return f"DATA_DELETION={on_sql}" 4401 4402 def maskingpolicycolumnconstraint_sql( 4403 self, expression: exp.MaskingPolicyColumnConstraint 4404 ) -> str: 4405 this = self.sql(expression, "this") 4406 expressions = self.expressions(expression, flat=True) 4407 expressions = f" USING ({expressions})" if expressions else "" 4408 return f"MASKING POLICY {this}{expressions}" 4409 4410 def gapfill_sql(self, expression: exp.GapFill) -> str: 4411 this = self.sql(expression, "this") 4412 this = f"TABLE {this}" 4413 return self.func("GAP_FILL", this, *[v for k, v in expression.args.items() if k != "this"]) 4414 4415 def scope_resolution(self, rhs: str, scope_name: str) -> str: 4416 return self.func("SCOPE_RESOLUTION", scope_name or None, rhs) 4417 4418 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4419 this = self.sql(expression, "this") 4420 expr = expression.expression 4421 4422 if isinstance(expr, exp.Func): 4423 # T-SQL's CLR functions are case sensitive 4424 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4425 else: 4426 expr = self.sql(expression, "expression") 4427 4428 return self.scope_resolution(expr, this) 4429 4430 def parsejson_sql(self, expression: exp.ParseJSON) -> str: 4431 if self.PARSE_JSON_NAME is None: 4432 return self.sql(expression.this) 4433 4434 return self.func(self.PARSE_JSON_NAME, expression.this, expression.expression) 4435 4436 def rand_sql(self, expression: exp.Rand) -> str: 4437 lower = self.sql(expression, "lower") 4438 upper = self.sql(expression, "upper") 4439 4440 if lower and upper: 4441 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4442 return self.func("RAND", expression.this) 4443 4444 def changes_sql(self, expression: exp.Changes) -> str: 4445 information = self.sql(expression, "information") 4446 information = f"INFORMATION => {information}" 4447 at_before = self.sql(expression, "at_before") 4448 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4449 end = self.sql(expression, "end") 4450 end = f"{self.seg('')}{end}" if end else "" 4451 4452 return f"CHANGES ({information}){at_before}{end}" 4453 4454 def pad_sql(self, expression: exp.Pad) -> str: 4455 prefix = "L" if expression.args.get("is_left") else "R" 4456 4457 fill_pattern = self.sql(expression, "fill_pattern") or None 4458 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4459 fill_pattern = "' '" 4460 4461 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern) 4462 4463 def summarize_sql(self, expression: exp.Summarize) -> str: 4464 table = " TABLE" if expression.args.get("table") else "" 4465 return f"SUMMARIZE{table} {self.sql(expression.this)}" 4466 4467 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4468 generate_series = exp.GenerateSeries(**expression.args) 4469 4470 parent = expression.parent 4471 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4472 parent = parent.parent 4473 4474 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4475 return self.sql(exp.Unnest(expressions=[generate_series])) 4476 4477 if isinstance(parent, exp.Select): 4478 self.unsupported("GenerateSeries projection unnesting is not supported.") 4479 4480 return self.sql(generate_series) 4481 4482 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4483 exprs = expression.expressions 4484 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4485 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4486 else: 4487 rhs = self.expressions(expression) 4488 4489 return self.func(name, expression.this, rhs or None) 4490 4491 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4492 if self.SUPPORTS_CONVERT_TIMEZONE: 4493 return self.function_fallback_sql(expression) 4494 4495 source_tz = expression.args.get("source_tz") 4496 target_tz = expression.args.get("target_tz") 4497 timestamp = expression.args.get("timestamp") 4498 4499 if source_tz and timestamp: 4500 timestamp = exp.AtTimeZone( 4501 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4502 ) 4503 4504 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4505 4506 return self.sql(expr) 4507 4508 def json_sql(self, expression: exp.JSON) -> str: 4509 this = self.sql(expression, "this") 4510 this = f" {this}" if this else "" 4511 4512 _with = expression.args.get("with") 4513 4514 if _with is None: 4515 with_sql = "" 4516 elif not _with: 4517 with_sql = " WITHOUT" 4518 else: 4519 with_sql = " WITH" 4520 4521 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4522 4523 return f"JSON{this}{with_sql}{unique_sql}" 4524 4525 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4526 def _generate_on_options(arg: t.Any) -> str: 4527 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4528 4529 path = self.sql(expression, "path") 4530 returning = self.sql(expression, "returning") 4531 returning = f" RETURNING {returning}" if returning else "" 4532 4533 on_condition = self.sql(expression, "on_condition") 4534 on_condition = f" {on_condition}" if on_condition else "" 4535 4536 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}") 4537 4538 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4539 else_ = "ELSE " if expression.args.get("else_") else "" 4540 condition = self.sql(expression, "expression") 4541 condition = f"WHEN {condition} THEN " if condition else else_ 4542 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4543 return f"{condition}{insert}" 4544 4545 def multitableinserts_sql(self, expression: exp.MultitableInserts) -> str: 4546 kind = self.sql(expression, "kind") 4547 expressions = self.seg(self.expressions(expression, sep=" ")) 4548 res = f"INSERT {kind}{expressions}{self.seg(self.sql(expression, 'source'))}" 4549 return res 4550 4551 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4552 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4553 empty = expression.args.get("empty") 4554 empty = ( 4555 f"DEFAULT {empty} ON EMPTY" 4556 if isinstance(empty, exp.Expression) 4557 else self.sql(expression, "empty") 4558 ) 4559 4560 error = expression.args.get("error") 4561 error = ( 4562 f"DEFAULT {error} ON ERROR" 4563 if isinstance(error, exp.Expression) 4564 else self.sql(expression, "error") 4565 ) 4566 4567 if error and empty: 4568 error = ( 4569 f"{empty} {error}" 4570 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4571 else f"{error} {empty}" 4572 ) 4573 empty = "" 4574 4575 null = self.sql(expression, "null") 4576 4577 return f"{empty}{error}{null}" 4578 4579 def jsonextractquote_sql(self, expression: exp.JSONExtractQuote) -> str: 4580 scalar = " ON SCALAR STRING" if expression.args.get("scalar") else "" 4581 return f"{self.sql(expression, 'option')} QUOTES{scalar}" 4582 4583 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4584 this = self.sql(expression, "this") 4585 path = self.sql(expression, "path") 4586 4587 passing = self.expressions(expression, "passing") 4588 passing = f" PASSING {passing}" if passing else "" 4589 4590 on_condition = self.sql(expression, "on_condition") 4591 on_condition = f" {on_condition}" if on_condition else "" 4592 4593 path = f"{path}{passing}{on_condition}" 4594 4595 return self.func("JSON_EXISTS", this, path) 4596 4597 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4598 array_agg = self.function_fallback_sql(expression) 4599 4600 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4601 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4602 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4603 parent = expression.parent 4604 if isinstance(parent, exp.Filter): 4605 parent_cond = parent.expression.this 4606 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4607 else: 4608 this = expression.this 4609 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4610 if this.find(exp.Column): 4611 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4612 this_sql = ( 4613 self.expressions(this) 4614 if isinstance(this, exp.Distinct) 4615 else self.sql(expression, "this") 4616 ) 4617 4618 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4619 4620 return array_agg 4621 4622 def apply_sql(self, expression: exp.Apply) -> str: 4623 this = self.sql(expression, "this") 4624 expr = self.sql(expression, "expression") 4625 4626 return f"{this} APPLY({expr})" 4627 4628 def grant_sql(self, expression: exp.Grant) -> str: 4629 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4630 4631 kind = self.sql(expression, "kind") 4632 kind = f" {kind}" if kind else "" 4633 4634 securable = self.sql(expression, "securable") 4635 securable = f" {securable}" if securable else "" 4636 4637 principals = self.expressions(expression, key="principals", flat=True) 4638 4639 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4640 4641 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}" 4642 4643 def grantprivilege_sql(self, expression: exp.GrantPrivilege): 4644 this = self.sql(expression, "this") 4645 columns = self.expressions(expression, flat=True) 4646 columns = f"({columns})" if columns else "" 4647 4648 return f"{this}{columns}" 4649 4650 def grantprincipal_sql(self, expression: exp.GrantPrincipal): 4651 this = self.sql(expression, "this") 4652 4653 kind = self.sql(expression, "kind") 4654 kind = f"{kind} " if kind else "" 4655 4656 return f"{kind}{this}" 4657 4658 def columns_sql(self, expression: exp.Columns): 4659 func = self.function_fallback_sql(expression) 4660 if expression.args.get("unpack"): 4661 func = f"*{func}" 4662 4663 return func 4664 4665 def overlay_sql(self, expression: exp.Overlay): 4666 this = self.sql(expression, "this") 4667 expr = self.sql(expression, "expression") 4668 from_sql = self.sql(expression, "from") 4669 for_sql = self.sql(expression, "for") 4670 for_sql = f" FOR {for_sql}" if for_sql else "" 4671 4672 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})" 4673 4674 @unsupported_args("format") 4675 def todouble_sql(self, expression: exp.ToDouble) -> str: 4676 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 4677 4678 def string_sql(self, expression: exp.String) -> str: 4679 this = expression.this 4680 zone = expression.args.get("zone") 4681 4682 if zone: 4683 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4684 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4685 # set for source_tz to transpile the time conversion before the STRING cast 4686 this = exp.ConvertTimezone( 4687 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4688 ) 4689 4690 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR)) 4691 4692 def median_sql(self, expression: exp.Median): 4693 if not self.SUPPORTS_MEDIAN: 4694 return self.sql( 4695 exp.PercentileCont(this=expression.this, expression=exp.Literal.number(0.5)) 4696 ) 4697 4698 return self.function_fallback_sql(expression) 4699 4700 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4701 filler = self.sql(expression, "this") 4702 filler = f" {filler}" if filler else "" 4703 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4704 return f"TRUNCATE{filler} {with_count}" 4705 4706 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4707 if self.SUPPORTS_UNIX_SECONDS: 4708 return self.function_fallback_sql(expression) 4709 4710 start_ts = exp.cast( 4711 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4712 ) 4713 4714 return self.sql( 4715 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4716 ) 4717 4718 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4719 dim = expression.expression 4720 4721 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4722 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4723 if not (dim.is_int and dim.name == "1"): 4724 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4725 dim = None 4726 4727 # If dimension is required but not specified, default initialize it 4728 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4729 dim = exp.Literal.number(1) 4730 4731 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim) 4732 4733 def attach_sql(self, expression: exp.Attach) -> str: 4734 this = self.sql(expression, "this") 4735 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4736 expressions = self.expressions(expression) 4737 expressions = f" ({expressions})" if expressions else "" 4738 4739 return f"ATTACH{exists_sql} {this}{expressions}" 4740 4741 def detach_sql(self, expression: exp.Detach) -> str: 4742 this = self.sql(expression, "this") 4743 exists_sql = " IF EXISTS" if expression.args.get("exists") else "" 4744 4745 return f"DETACH{exists_sql} {this}" 4746 4747 def attachoption_sql(self, expression: exp.AttachOption) -> str: 4748 this = self.sql(expression, "this") 4749 value = self.sql(expression, "expression") 4750 value = f" {value}" if value else "" 4751 return f"{this}{value}" 4752 4753 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4754 this_sql = self.sql(expression, "this") 4755 if isinstance(expression.this, exp.Table): 4756 this_sql = f"TABLE {this_sql}" 4757 4758 return self.func( 4759 "FEATURES_AT_TIME", 4760 this_sql, 4761 expression.args.get("time"), 4762 expression.args.get("num_rows"), 4763 expression.args.get("ignore_feature_nulls"), 4764 ) 4765 4766 def watermarkcolumnconstraint_sql(self, expression: exp.WatermarkColumnConstraint) -> str: 4767 return ( 4768 f"WATERMARK FOR {self.sql(expression, 'this')} AS {self.sql(expression, 'expression')}" 4769 ) 4770 4771 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4772 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4773 encode = f"{encode} {self.sql(expression, 'this')}" 4774 4775 properties = expression.args.get("properties") 4776 if properties: 4777 encode = f"{encode} {self.properties(properties)}" 4778 4779 return encode 4780 4781 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4782 this = self.sql(expression, "this") 4783 include = f"INCLUDE {this}" 4784 4785 column_def = self.sql(expression, "column_def") 4786 if column_def: 4787 include = f"{include} {column_def}" 4788 4789 alias = self.sql(expression, "alias") 4790 if alias: 4791 include = f"{include} AS {alias}" 4792 4793 return include 4794 4795 def xmlelement_sql(self, expression: exp.XMLElement) -> str: 4796 name = f"NAME {self.sql(expression, 'this')}" 4797 return self.func("XMLELEMENT", name, *expression.expressions) 4798 4799 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4800 partitions = self.expressions(expression, "partition_expressions") 4801 create = self.expressions(expression, "create_expressions") 4802 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}" 4803 4804 def partitionbyrangepropertydynamic_sql( 4805 self, expression: exp.PartitionByRangePropertyDynamic 4806 ) -> str: 4807 start = self.sql(expression, "start") 4808 end = self.sql(expression, "end") 4809 4810 every = expression.args["every"] 4811 if isinstance(every, exp.Interval) and every.this.is_string: 4812 every.this.replace(exp.Literal.number(every.name)) 4813 4814 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}" 4815 4816 def unpivotcolumns_sql(self, expression: exp.UnpivotColumns) -> str: 4817 name = self.sql(expression, "this") 4818 values = self.expressions(expression, flat=True) 4819 4820 return f"NAME {name} VALUE {values}" 4821 4822 def analyzesample_sql(self, expression: exp.AnalyzeSample) -> str: 4823 kind = self.sql(expression, "kind") 4824 sample = self.sql(expression, "sample") 4825 return f"SAMPLE {sample} {kind}" 4826 4827 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4828 kind = self.sql(expression, "kind") 4829 option = self.sql(expression, "option") 4830 option = f" {option}" if option else "" 4831 this = self.sql(expression, "this") 4832 this = f" {this}" if this else "" 4833 columns = self.expressions(expression) 4834 columns = f" {columns}" if columns else "" 4835 return f"{kind}{option} STATISTICS{this}{columns}" 4836 4837 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4838 this = self.sql(expression, "this") 4839 columns = self.expressions(expression) 4840 inner_expression = self.sql(expression, "expression") 4841 inner_expression = f" {inner_expression}" if inner_expression else "" 4842 update_options = self.sql(expression, "update_options") 4843 update_options = f" {update_options} UPDATE" if update_options else "" 4844 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}" 4845 4846 def analyzedelete_sql(self, expression: exp.AnalyzeDelete) -> str: 4847 kind = self.sql(expression, "kind") 4848 kind = f" {kind}" if kind else "" 4849 return f"DELETE{kind} STATISTICS" 4850 4851 def analyzelistchainedrows_sql(self, expression: exp.AnalyzeListChainedRows) -> str: 4852 inner_expression = self.sql(expression, "expression") 4853 return f"LIST CHAINED ROWS{inner_expression}" 4854 4855 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4856 kind = self.sql(expression, "kind") 4857 this = self.sql(expression, "this") 4858 this = f" {this}" if this else "" 4859 inner_expression = self.sql(expression, "expression") 4860 return f"VALIDATE {kind}{this}{inner_expression}" 4861 4862 def analyze_sql(self, expression: exp.Analyze) -> str: 4863 options = self.expressions(expression, key="options", sep=" ") 4864 options = f" {options}" if options else "" 4865 kind = self.sql(expression, "kind") 4866 kind = f" {kind}" if kind else "" 4867 this = self.sql(expression, "this") 4868 this = f" {this}" if this else "" 4869 mode = self.sql(expression, "mode") 4870 mode = f" {mode}" if mode else "" 4871 properties = self.sql(expression, "properties") 4872 properties = f" {properties}" if properties else "" 4873 partition = self.sql(expression, "partition") 4874 partition = f" {partition}" if partition else "" 4875 inner_expression = self.sql(expression, "expression") 4876 inner_expression = f" {inner_expression}" if inner_expression else "" 4877 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}" 4878 4879 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4880 this = self.sql(expression, "this") 4881 namespaces = self.expressions(expression, key="namespaces") 4882 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4883 passing = self.expressions(expression, key="passing") 4884 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4885 columns = self.expressions(expression, key="columns") 4886 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4887 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4888 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}" 4889 4890 def xmlnamespace_sql(self, expression: exp.XMLNamespace) -> str: 4891 this = self.sql(expression, "this") 4892 return this if isinstance(expression.this, exp.Alias) else f"DEFAULT {this}" 4893 4894 def export_sql(self, expression: exp.Export) -> str: 4895 this = self.sql(expression, "this") 4896 connection = self.sql(expression, "connection") 4897 connection = f"WITH CONNECTION {connection} " if connection else "" 4898 options = self.sql(expression, "options") 4899 return f"EXPORT DATA {connection}{options} AS {this}" 4900 4901 def declare_sql(self, expression: exp.Declare) -> str: 4902 return f"DECLARE {self.expressions(expression, flat=True)}" 4903 4904 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4905 variable = self.sql(expression, "this") 4906 default = self.sql(expression, "default") 4907 default = f" = {default}" if default else "" 4908 4909 kind = self.sql(expression, "kind") 4910 if isinstance(expression.args.get("kind"), exp.Schema): 4911 kind = f"TABLE {kind}" 4912 4913 return f"{variable} AS {kind}{default}" 4914 4915 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4916 kind = self.sql(expression, "kind") 4917 this = self.sql(expression, "this") 4918 set = self.sql(expression, "expression") 4919 using = self.sql(expression, "using") 4920 using = f" USING {using}" if using else "" 4921 4922 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4923 4924 return f"{kind_sql} {this} SET {set}{using}" 4925 4926 def parameterizedagg_sql(self, expression: exp.ParameterizedAgg) -> str: 4927 params = self.expressions(expression, key="params", flat=True) 4928 return self.func(expression.name, *expression.expressions) + f"({params})" 4929 4930 def anonymousaggfunc_sql(self, expression: exp.AnonymousAggFunc) -> str: 4931 return self.func(expression.name, *expression.expressions) 4932 4933 def combinedaggfunc_sql(self, expression: exp.CombinedAggFunc) -> str: 4934 return self.anonymousaggfunc_sql(expression) 4935 4936 def combinedparameterizedagg_sql(self, expression: exp.CombinedParameterizedAgg) -> str: 4937 return self.parameterizedagg_sql(expression) 4938 4939 def show_sql(self, expression: exp.Show) -> str: 4940 self.unsupported("Unsupported SHOW statement") 4941 return "" 4942 4943 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 4944 # Snowflake GET/PUT statements: 4945 # PUT <file> <internalStage> <properties> 4946 # GET <internalStage> <file> <properties> 4947 props = expression.args.get("properties") 4948 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 4949 this = self.sql(expression, "this") 4950 target = self.sql(expression, "target") 4951 4952 if isinstance(expression, exp.Put): 4953 return f"PUT {this} {target}{props_sql}" 4954 else: 4955 return f"GET {target} {this}{props_sql}" 4956 4957 def translatecharacters_sql(self, expression: exp.TranslateCharacters): 4958 this = self.sql(expression, "this") 4959 expr = self.sql(expression, "expression") 4960 with_error = " WITH ERROR" if expression.args.get("with_error") else "" 4961 return f"TRANSLATE({this} USING {expr}{with_error})"
Generator converts a given syntax tree to the corresponding SQL string.
Arguments:
- pretty: Whether to format the produced SQL string. Default: False.
- identify: Determines when an identifier should be quoted. Possible values are: False (default): Never quote, except in cases where it's mandatory by the dialect. True or 'always': Always quote. 'safe': Only quote identifiers that are case insensitive.
- normalize: Whether to normalize identifiers to lowercase. Default: False.
- pad: The pad size in a formatted string. For example, this affects the indentation of a projection in a query, relative to its nesting level. Default: 2.
- indent: The indentation size in a formatted string. For example, this affects the
indentation of subqueries and filters under a
WHEREclause. Default: 2. - normalize_functions: How to normalize function names. Possible values are: "upper" or True (default): Convert names to uppercase. "lower": Convert names to lowercase. False: Disables function name normalization.
- unsupported_level: Determines the generator's behavior when it encounters unsupported expressions. Default ErrorLevel.WARN.
- max_unsupported: Maximum number of unsupported messages to include in a raised UnsupportedError. This is only relevant if unsupported_level is ErrorLevel.RAISE. Default: 3
- leading_comma: Whether the comma is leading or trailing in select expressions. This is only relevant when generating in pretty mode. Default: False
- max_text_width: The max number of characters in a segment before creating new lines in pretty mode. The default is on the smaller end because the length only represents a segment and not the true line length. Default: 80
- comments: Whether to preserve comments in the output SQL code. Default: True
Generator( pretty: Optional[bool] = None, identify: str | bool = False, normalize: bool = False, pad: int = 2, indent: int = 2, normalize_functions: Union[str, bool, NoneType] = None, unsupported_level: sqlglot.errors.ErrorLevel = <ErrorLevel.WARN: 'WARN'>, max_unsupported: int = 3, leading_comma: bool = False, max_text_width: int = 80, comments: bool = True, dialect: Union[str, sqlglot.dialects.Dialect, Type[sqlglot.dialects.Dialect], NoneType] = None)
697 def __init__( 698 self, 699 pretty: t.Optional[bool] = None, 700 identify: str | bool = False, 701 normalize: bool = False, 702 pad: int = 2, 703 indent: int = 2, 704 normalize_functions: t.Optional[str | bool] = None, 705 unsupported_level: ErrorLevel = ErrorLevel.WARN, 706 max_unsupported: int = 3, 707 leading_comma: bool = False, 708 max_text_width: int = 80, 709 comments: bool = True, 710 dialect: DialectType = None, 711 ): 712 import sqlglot 713 from sqlglot.dialects import Dialect 714 715 self.pretty = pretty if pretty is not None else sqlglot.pretty 716 self.identify = identify 717 self.normalize = normalize 718 self.pad = pad 719 self._indent = indent 720 self.unsupported_level = unsupported_level 721 self.max_unsupported = max_unsupported 722 self.leading_comma = leading_comma 723 self.max_text_width = max_text_width 724 self.comments = comments 725 self.dialect = Dialect.get_or_raise(dialect) 726 727 # This is both a Dialect property and a Generator argument, so we prioritize the latter 728 self.normalize_functions = ( 729 self.dialect.NORMALIZE_FUNCTIONS if normalize_functions is None else normalize_functions 730 ) 731 732 self.unsupported_messages: t.List[str] = [] 733 self._escaped_quote_end: str = ( 734 self.dialect.tokenizer_class.STRING_ESCAPES[0] + self.dialect.QUOTE_END 735 ) 736 self._escaped_identifier_end = self.dialect.IDENTIFIER_END * 2 737 738 self._next_name = name_sequence("_t") 739 740 self._identifier_start = self.dialect.IDENTIFIER_START 741 self._identifier_end = self.dialect.IDENTIFIER_END 742 743 self._quote_json_path_key_using_brackets = True
TRANSFORMS: Dict[Type[sqlglot.expressions.Expression], Callable[..., str]] =
{<class 'sqlglot.expressions.JSONPathFilter'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathKey'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRecursive'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathRoot'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathScript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSelector'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSlice'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathSubscript'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathUnion'>: <function <lambda>>, <class 'sqlglot.expressions.JSONPathWildcard'>: <function <lambda>>, <class 'sqlglot.expressions.AllowedValuesProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeColumns'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AnalyzeWith'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayContainsAll'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ArrayOverlaps'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.BackupProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CaseSpecificColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Ceil'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CharacterSetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CollateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CommentColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConnectByRoot'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ConvertToCharset'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.CredentialsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DateFormatColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DefaultColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.DynamicProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EmptyProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EncodeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EnviromentProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.EphemeralColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExcludeColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Except'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ExternalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Floor'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Get'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.GlobalProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.HeapProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IcebergProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InheritsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InlineLengthColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.InputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Intersect'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.IntervalSpan'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Int64'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LanguageProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LocationProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.LogProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.MaterializedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NonClusteredColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.NotForReplicationColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnCommitProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OnUpdateColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Operator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.OutputModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PathColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionedByBucket'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PartitionByTruncate'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.PivotAny'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ProjectionPolicyColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Put'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ReturnsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SampleProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecureProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetConfigProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SetProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SettingsProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SharingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StabilityProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Stream'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StreamingTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.StrictProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.SwapTable'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Tags'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TemporaryProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TitleColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ToTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransformModelProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.TransientProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Union'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UnloggedProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UsingData'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.Uuid'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.UppercaseColumnConstraint'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VarMap'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.VolatileProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithProcedureOptions'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.WithOperator'>: <function Generator.<lambda>>, <class 'sqlglot.expressions.ForceProperty'>: <function Generator.<lambda>>}
SUPPORTED_JSON_PATH_PARTS =
{<class 'sqlglot.expressions.JSONPathSelector'>, <class 'sqlglot.expressions.JSONPathSlice'>, <class 'sqlglot.expressions.JSONPathScript'>, <class 'sqlglot.expressions.JSONPathUnion'>, <class 'sqlglot.expressions.JSONPathRoot'>, <class 'sqlglot.expressions.JSONPathRecursive'>, <class 'sqlglot.expressions.JSONPathKey'>, <class 'sqlglot.expressions.JSONPathWildcard'>, <class 'sqlglot.expressions.JSONPathFilter'>, <class 'sqlglot.expressions.JSONPathSubscript'>}
TYPE_MAPPING =
{<Type.DATETIME2: 'DATETIME2'>: 'TIMESTAMP', <Type.NCHAR: 'NCHAR'>: 'CHAR', <Type.NVARCHAR: 'NVARCHAR'>: 'VARCHAR', <Type.MEDIUMTEXT: 'MEDIUMTEXT'>: 'TEXT', <Type.LONGTEXT: 'LONGTEXT'>: 'TEXT', <Type.TINYTEXT: 'TINYTEXT'>: 'TEXT', <Type.BLOB: 'BLOB'>: 'VARBINARY', <Type.MEDIUMBLOB: 'MEDIUMBLOB'>: 'BLOB', <Type.LONGBLOB: 'LONGBLOB'>: 'BLOB', <Type.TINYBLOB: 'TINYBLOB'>: 'BLOB', <Type.INET: 'INET'>: 'INET', <Type.ROWVERSION: 'ROWVERSION'>: 'VARBINARY', <Type.SMALLDATETIME: 'SMALLDATETIME'>: 'TIMESTAMP'}
TIME_PART_SINGULARS =
{'MICROSECONDS': 'MICROSECOND', 'SECONDS': 'SECOND', 'MINUTES': 'MINUTE', 'HOURS': 'HOUR', 'DAYS': 'DAY', 'WEEKS': 'WEEK', 'MONTHS': 'MONTH', 'QUARTERS': 'QUARTER', 'YEARS': 'YEAR'}
AFTER_HAVING_MODIFIER_TRANSFORMS =
{'cluster': <function Generator.<lambda>>, 'distribute': <function Generator.<lambda>>, 'sort': <function Generator.<lambda>>, 'windows': <function Generator.<lambda>>, 'qualify': <function Generator.<lambda>>}
PROPERTIES_LOCATION =
{<class 'sqlglot.expressions.AllowedValuesProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AlgorithmProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.AutoIncrementProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.AutoRefreshProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BackupProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.BlockCompressionProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CharacterSetProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ChecksumProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.CollateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.CopyGrantsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Cluster'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ClusteredByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistributedByProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DuplicateKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DataBlocksizeProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.DataDeletionProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DefinerProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DictRange'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DynamicProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.DistKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.DistStyleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EmptyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EncodeProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.EngineProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.EnviromentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExecuteAsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ExternalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.FallbackProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.FileFormatProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.FreespaceProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.GlobalProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.HeapProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.InheritsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IcebergProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.IncludeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.InputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.IsolatedLoadingProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.JournalProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.LanguageProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LikeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LocationProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.LockingProperty'>: <Location.POST_ALIAS: 'POST_ALIAS'>, <class 'sqlglot.expressions.LogProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.MaterializedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.MergeBlockRatioProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.NoPrimaryIndexProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.OnProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OnCommitProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.Order'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.OutputModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PartitionedByProperty'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.PartitionedOfProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.PrimaryKey'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Property'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.RemoteWithConnectionModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ReturnsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatDelimitedProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.RowFormatSerdeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SampleProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SchemaCommentProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SecureProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SecurityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SerdeProperties'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Set'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SettingsProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SetProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.SetConfigProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SharingProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SequenceProperties'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.SortKeyProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlReadWriteProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.SqlSecurityProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StabilityProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StorageHandlerProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.StreamingTableProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.StrictProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.Tags'>: <Location.POST_WITH: 'POST_WITH'>, <class 'sqlglot.expressions.TemporaryProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.ToTableProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.TransientProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.TransformModelProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.MergeTreeTTL'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.UnloggedProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.UsingTemplateProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ViewAttributeProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.VolatileProperty'>: <Location.POST_CREATE: 'POST_CREATE'>, <class 'sqlglot.expressions.WithDataProperty'>: <Location.POST_EXPRESSION: 'POST_EXPRESSION'>, <class 'sqlglot.expressions.WithJournalTableProperty'>: <Location.POST_NAME: 'POST_NAME'>, <class 'sqlglot.expressions.WithProcedureOptions'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSchemaBindingProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.WithSystemVersioningProperty'>: <Location.POST_SCHEMA: 'POST_SCHEMA'>, <class 'sqlglot.expressions.ForceProperty'>: <Location.POST_CREATE: 'POST_CREATE'>}
WITH_SEPARATED_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Command'>, <class 'sqlglot.expressions.Create'>, <class 'sqlglot.expressions.Describe'>, <class 'sqlglot.expressions.Delete'>, <class 'sqlglot.expressions.Drop'>, <class 'sqlglot.expressions.From'>, <class 'sqlglot.expressions.Insert'>, <class 'sqlglot.expressions.Join'>, <class 'sqlglot.expressions.MultitableInserts'>, <class 'sqlglot.expressions.Select'>, <class 'sqlglot.expressions.SetOperation'>, <class 'sqlglot.expressions.Update'>, <class 'sqlglot.expressions.Where'>, <class 'sqlglot.expressions.With'>)
EXCLUDE_COMMENTS: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Binary'>, <class 'sqlglot.expressions.SetOperation'>)
UNWRAPPED_INTERVAL_VALUES: Tuple[Type[sqlglot.expressions.Expression], ...] =
(<class 'sqlglot.expressions.Column'>, <class 'sqlglot.expressions.Literal'>, <class 'sqlglot.expressions.Neg'>, <class 'sqlglot.expressions.Paren'>)
PARAMETERIZABLE_TEXT_TYPES =
{<Type.NCHAR: 'NCHAR'>, <Type.VARCHAR: 'VARCHAR'>, <Type.CHAR: 'CHAR'>, <Type.NVARCHAR: 'NVARCHAR'>}
745 def generate(self, expression: exp.Expression, copy: bool = True) -> str: 746 """ 747 Generates the SQL string corresponding to the given syntax tree. 748 749 Args: 750 expression: The syntax tree. 751 copy: Whether to copy the expression. The generator performs mutations so 752 it is safer to copy. 753 754 Returns: 755 The SQL string corresponding to `expression`. 756 """ 757 if copy: 758 expression = expression.copy() 759 760 expression = self.preprocess(expression) 761 762 self.unsupported_messages = [] 763 sql = self.sql(expression).strip() 764 765 if self.pretty: 766 sql = sql.replace(self.SENTINEL_LINE_BREAK, "\n") 767 768 if self.unsupported_level == ErrorLevel.IGNORE: 769 return sql 770 771 if self.unsupported_level == ErrorLevel.WARN: 772 for msg in self.unsupported_messages: 773 logger.warning(msg) 774 elif self.unsupported_level == ErrorLevel.RAISE and self.unsupported_messages: 775 raise UnsupportedError(concat_messages(self.unsupported_messages, self.max_unsupported)) 776 777 return sql
Generates the SQL string corresponding to the given syntax tree.
Arguments:
- expression: The syntax tree.
- copy: Whether to copy the expression. The generator performs mutations so it is safer to copy.
Returns:
The SQL string corresponding to
expression.
def
preprocess( self, expression: sqlglot.expressions.Expression) -> sqlglot.expressions.Expression:
779 def preprocess(self, expression: exp.Expression) -> exp.Expression: 780 """Apply generic preprocessing transformations to a given expression.""" 781 expression = self._move_ctes_to_top_level(expression) 782 783 if self.ENSURE_BOOLS: 784 from sqlglot.transforms import ensure_bools 785 786 expression = ensure_bools(expression) 787 788 return expression
Apply generic preprocessing transformations to a given expression.
def
maybe_comment( self, sql: str, expression: Optional[sqlglot.expressions.Expression] = None, comments: Optional[List[str]] = None, separated: bool = False) -> str:
817 def maybe_comment( 818 self, 819 sql: str, 820 expression: t.Optional[exp.Expression] = None, 821 comments: t.Optional[t.List[str]] = None, 822 separated: bool = False, 823 ) -> str: 824 comments = ( 825 ((expression and expression.comments) if comments is None else comments) # type: ignore 826 if self.comments 827 else None 828 ) 829 830 if not comments or isinstance(expression, self.EXCLUDE_COMMENTS): 831 return sql 832 833 comments_sql = " ".join( 834 f"/*{self.pad_comment(comment)}*/" for comment in comments if comment 835 ) 836 837 if not comments_sql: 838 return sql 839 840 comments_sql = self._replace_line_breaks(comments_sql) 841 842 if separated or isinstance(expression, self.WITH_SEPARATED_COMMENTS): 843 return ( 844 f"{self.sep()}{comments_sql}{sql}" 845 if not sql or sql[0].isspace() 846 else f"{comments_sql}{self.sep()}{sql}" 847 ) 848 849 return f"{sql} {comments_sql}"
851 def wrap(self, expression: exp.Expression | str) -> str: 852 this_sql = ( 853 self.sql(expression) 854 if isinstance(expression, exp.UNWRAPPED_QUERIES) 855 else self.sql(expression, "this") 856 ) 857 if not this_sql: 858 return "()" 859 860 this_sql = self.indent(this_sql, level=1, pad=0) 861 return f"({self.sep('')}{this_sql}{self.seg(')', sep='')}"
def
indent( self, sql: str, level: int = 0, pad: Optional[int] = None, skip_first: bool = False, skip_last: bool = False) -> str:
877 def indent( 878 self, 879 sql: str, 880 level: int = 0, 881 pad: t.Optional[int] = None, 882 skip_first: bool = False, 883 skip_last: bool = False, 884 ) -> str: 885 if not self.pretty or not sql: 886 return sql 887 888 pad = self.pad if pad is None else pad 889 lines = sql.split("\n") 890 891 return "\n".join( 892 ( 893 line 894 if (skip_first and i == 0) or (skip_last and i == len(lines) - 1) 895 else f"{' ' * (level * self._indent + pad)}{line}" 896 ) 897 for i, line in enumerate(lines) 898 )
def
sql( self, expression: Union[str, sqlglot.expressions.Expression, NoneType], key: Optional[str] = None, comment: bool = True) -> str:
900 def sql( 901 self, 902 expression: t.Optional[str | exp.Expression], 903 key: t.Optional[str] = None, 904 comment: bool = True, 905 ) -> str: 906 if not expression: 907 return "" 908 909 if isinstance(expression, str): 910 return expression 911 912 if key: 913 value = expression.args.get(key) 914 if value: 915 return self.sql(value) 916 return "" 917 918 transform = self.TRANSFORMS.get(expression.__class__) 919 920 if callable(transform): 921 sql = transform(self, expression) 922 elif isinstance(expression, exp.Expression): 923 exp_handler_name = f"{expression.key}_sql" 924 925 if hasattr(self, exp_handler_name): 926 sql = getattr(self, exp_handler_name)(expression) 927 elif isinstance(expression, exp.Func): 928 sql = self.function_fallback_sql(expression) 929 elif isinstance(expression, exp.Property): 930 sql = self.property_sql(expression) 931 else: 932 raise ValueError(f"Unsupported expression type {expression.__class__.__name__}") 933 else: 934 raise ValueError(f"Expected an Expression. Received {type(expression)}: {expression}") 935 936 return self.maybe_comment(sql, expression) if self.comments and comment else sql
943 def cache_sql(self, expression: exp.Cache) -> str: 944 lazy = " LAZY" if expression.args.get("lazy") else "" 945 table = self.sql(expression, "this") 946 options = expression.args.get("options") 947 options = f" OPTIONS({self.sql(options[0])} = {self.sql(options[1])})" if options else "" 948 sql = self.sql(expression, "expression") 949 sql = f" AS{self.sep()}{sql}" if sql else "" 950 sql = f"CACHE{lazy} TABLE {table}{options}{sql}" 951 return self.prepend_ctes(expression, sql)
953 def characterset_sql(self, expression: exp.CharacterSet) -> str: 954 if isinstance(expression.parent, exp.Cast): 955 return f"CHAR CHARACTER SET {self.sql(expression, 'this')}" 956 default = "DEFAULT " if expression.args.get("default") else "" 957 return f"{default}CHARACTER SET={self.sql(expression, 'this')}"
971 def column_sql(self, expression: exp.Column) -> str: 972 join_mark = " (+)" if expression.args.get("join_mark") else "" 973 974 if join_mark and not self.dialect.SUPPORTS_COLUMN_JOIN_MARKS: 975 join_mark = "" 976 self.unsupported("Outer join syntax using the (+) operator is not supported.") 977 978 return f"{self.column_parts(expression)}{join_mark}"
986 def columndef_sql(self, expression: exp.ColumnDef, sep: str = " ") -> str: 987 column = self.sql(expression, "this") 988 kind = self.sql(expression, "kind") 989 constraints = self.expressions(expression, key="constraints", sep=" ", flat=True) 990 exists = "IF NOT EXISTS " if expression.args.get("exists") else "" 991 kind = f"{sep}{kind}" if kind else "" 992 constraints = f" {constraints}" if constraints else "" 993 position = self.sql(expression, "position") 994 position = f" {position}" if position else "" 995 996 if expression.find(exp.ComputedColumnConstraint) and not self.COMPUTED_COLUMN_WITH_TYPE: 997 kind = "" 998 999 return f"{exists}{column}{kind}{constraints}{position}"
def
computedcolumnconstraint_sql(self, expression: sqlglot.expressions.ComputedColumnConstraint) -> str:
1006 def computedcolumnconstraint_sql(self, expression: exp.ComputedColumnConstraint) -> str: 1007 this = self.sql(expression, "this") 1008 if expression.args.get("not_null"): 1009 persisted = " PERSISTED NOT NULL" 1010 elif expression.args.get("persisted"): 1011 persisted = " PERSISTED" 1012 else: 1013 persisted = "" 1014 return f"AS {this}{persisted}"
def
compresscolumnconstraint_sql(self, expression: sqlglot.expressions.CompressColumnConstraint) -> str:
def
generatedasidentitycolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsIdentityColumnConstraint) -> str:
1027 def generatedasidentitycolumnconstraint_sql( 1028 self, expression: exp.GeneratedAsIdentityColumnConstraint 1029 ) -> str: 1030 this = "" 1031 if expression.this is not None: 1032 on_null = " ON NULL" if expression.args.get("on_null") else "" 1033 this = " ALWAYS" if expression.this else f" BY DEFAULT{on_null}" 1034 1035 start = expression.args.get("start") 1036 start = f"START WITH {start}" if start else "" 1037 increment = expression.args.get("increment") 1038 increment = f" INCREMENT BY {increment}" if increment else "" 1039 minvalue = expression.args.get("minvalue") 1040 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1041 maxvalue = expression.args.get("maxvalue") 1042 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1043 cycle = expression.args.get("cycle") 1044 cycle_sql = "" 1045 1046 if cycle is not None: 1047 cycle_sql = f"{' NO' if not cycle else ''} CYCLE" 1048 cycle_sql = cycle_sql.strip() if not start and not increment else cycle_sql 1049 1050 sequence_opts = "" 1051 if start or increment or cycle_sql: 1052 sequence_opts = f"{start}{increment}{minvalue}{maxvalue}{cycle_sql}" 1053 sequence_opts = f" ({sequence_opts.strip()})" 1054 1055 expr = self.sql(expression, "expression") 1056 expr = f"({expr})" if expr else "IDENTITY" 1057 1058 return f"GENERATED{this} AS {expr}{sequence_opts}"
def
generatedasrowcolumnconstraint_sql( self, expression: sqlglot.expressions.GeneratedAsRowColumnConstraint) -> str:
1060 def generatedasrowcolumnconstraint_sql( 1061 self, expression: exp.GeneratedAsRowColumnConstraint 1062 ) -> str: 1063 start = "START" if expression.args.get("start") else "END" 1064 hidden = " HIDDEN" if expression.args.get("hidden") else "" 1065 return f"GENERATED ALWAYS AS ROW {start}{hidden}"
def
periodforsystemtimeconstraint_sql( self, expression: sqlglot.expressions.PeriodForSystemTimeConstraint) -> str:
def
notnullcolumnconstraint_sql(self, expression: sqlglot.expressions.NotNullColumnConstraint) -> str:
def
transformcolumnconstraint_sql(self, expression: sqlglot.expressions.TransformColumnConstraint) -> str:
def
primarykeycolumnconstraint_sql(self, expression: sqlglot.expressions.PrimaryKeyColumnConstraint) -> str:
1078 def primarykeycolumnconstraint_sql(self, expression: exp.PrimaryKeyColumnConstraint) -> str: 1079 desc = expression.args.get("desc") 1080 if desc is not None: 1081 return f"PRIMARY KEY{' DESC' if desc else ' ASC'}" 1082 options = self.expressions(expression, key="options", flat=True, sep=" ") 1083 options = f" {options}" if options else "" 1084 return f"PRIMARY KEY{options}"
def
uniquecolumnconstraint_sql(self, expression: sqlglot.expressions.UniqueColumnConstraint) -> str:
1086 def uniquecolumnconstraint_sql(self, expression: exp.UniqueColumnConstraint) -> str: 1087 this = self.sql(expression, "this") 1088 this = f" {this}" if this else "" 1089 index_type = expression.args.get("index_type") 1090 index_type = f" USING {index_type}" if index_type else "" 1091 on_conflict = self.sql(expression, "on_conflict") 1092 on_conflict = f" {on_conflict}" if on_conflict else "" 1093 nulls_sql = " NULLS NOT DISTINCT" if expression.args.get("nulls") else "" 1094 options = self.expressions(expression, key="options", flat=True, sep=" ") 1095 options = f" {options}" if options else "" 1096 return f"UNIQUE{nulls_sql}{this}{index_type}{on_conflict}{options}"
1101 def create_sql(self, expression: exp.Create) -> str: 1102 kind = self.sql(expression, "kind") 1103 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1104 properties = expression.args.get("properties") 1105 properties_locs = self.locate_properties(properties) if properties else defaultdict() 1106 1107 this = self.createable_sql(expression, properties_locs) 1108 1109 properties_sql = "" 1110 if properties_locs.get(exp.Properties.Location.POST_SCHEMA) or properties_locs.get( 1111 exp.Properties.Location.POST_WITH 1112 ): 1113 properties_sql = self.sql( 1114 exp.Properties( 1115 expressions=[ 1116 *properties_locs[exp.Properties.Location.POST_SCHEMA], 1117 *properties_locs[exp.Properties.Location.POST_WITH], 1118 ] 1119 ) 1120 ) 1121 1122 if properties_locs.get(exp.Properties.Location.POST_SCHEMA): 1123 properties_sql = self.sep() + properties_sql 1124 elif not self.pretty: 1125 # Standalone POST_WITH properties need a leading whitespace in non-pretty mode 1126 properties_sql = f" {properties_sql}" 1127 1128 begin = " BEGIN" if expression.args.get("begin") else "" 1129 end = " END" if expression.args.get("end") else "" 1130 1131 expression_sql = self.sql(expression, "expression") 1132 if expression_sql: 1133 expression_sql = f"{begin}{self.sep()}{expression_sql}{end}" 1134 1135 if self.CREATE_FUNCTION_RETURN_AS or not isinstance(expression.expression, exp.Return): 1136 postalias_props_sql = "" 1137 if properties_locs.get(exp.Properties.Location.POST_ALIAS): 1138 postalias_props_sql = self.properties( 1139 exp.Properties( 1140 expressions=properties_locs[exp.Properties.Location.POST_ALIAS] 1141 ), 1142 wrapped=False, 1143 ) 1144 postalias_props_sql = f" {postalias_props_sql}" if postalias_props_sql else "" 1145 expression_sql = f" AS{postalias_props_sql}{expression_sql}" 1146 1147 postindex_props_sql = "" 1148 if properties_locs.get(exp.Properties.Location.POST_INDEX): 1149 postindex_props_sql = self.properties( 1150 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_INDEX]), 1151 wrapped=False, 1152 prefix=" ", 1153 ) 1154 1155 indexes = self.expressions(expression, key="indexes", indent=False, sep=" ") 1156 indexes = f" {indexes}" if indexes else "" 1157 index_sql = indexes + postindex_props_sql 1158 1159 replace = " OR REPLACE" if expression.args.get("replace") else "" 1160 refresh = " OR REFRESH" if expression.args.get("refresh") else "" 1161 unique = " UNIQUE" if expression.args.get("unique") else "" 1162 1163 clustered = expression.args.get("clustered") 1164 if clustered is None: 1165 clustered_sql = "" 1166 elif clustered: 1167 clustered_sql = " CLUSTERED COLUMNSTORE" 1168 else: 1169 clustered_sql = " NONCLUSTERED COLUMNSTORE" 1170 1171 postcreate_props_sql = "" 1172 if properties_locs.get(exp.Properties.Location.POST_CREATE): 1173 postcreate_props_sql = self.properties( 1174 exp.Properties(expressions=properties_locs[exp.Properties.Location.POST_CREATE]), 1175 sep=" ", 1176 prefix=" ", 1177 wrapped=False, 1178 ) 1179 1180 modifiers = "".join((clustered_sql, replace, refresh, unique, postcreate_props_sql)) 1181 1182 postexpression_props_sql = "" 1183 if properties_locs.get(exp.Properties.Location.POST_EXPRESSION): 1184 postexpression_props_sql = self.properties( 1185 exp.Properties( 1186 expressions=properties_locs[exp.Properties.Location.POST_EXPRESSION] 1187 ), 1188 sep=" ", 1189 prefix=" ", 1190 wrapped=False, 1191 ) 1192 1193 concurrently = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1194 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 1195 no_schema_binding = ( 1196 " WITH NO SCHEMA BINDING" if expression.args.get("no_schema_binding") else "" 1197 ) 1198 1199 clone = self.sql(expression, "clone") 1200 clone = f" {clone}" if clone else "" 1201 1202 if kind in self.EXPRESSION_PRECEDES_PROPERTIES_CREATABLES: 1203 properties_expression = f"{expression_sql}{properties_sql}" 1204 else: 1205 properties_expression = f"{properties_sql}{expression_sql}" 1206 1207 expression_sql = f"CREATE{modifiers} {kind}{concurrently}{exists_sql} {this}{properties_expression}{postexpression_props_sql}{index_sql}{no_schema_binding}{clone}" 1208 return self.prepend_ctes(expression, expression_sql)
1210 def sequenceproperties_sql(self, expression: exp.SequenceProperties) -> str: 1211 start = self.sql(expression, "start") 1212 start = f"START WITH {start}" if start else "" 1213 increment = self.sql(expression, "increment") 1214 increment = f" INCREMENT BY {increment}" if increment else "" 1215 minvalue = self.sql(expression, "minvalue") 1216 minvalue = f" MINVALUE {minvalue}" if minvalue else "" 1217 maxvalue = self.sql(expression, "maxvalue") 1218 maxvalue = f" MAXVALUE {maxvalue}" if maxvalue else "" 1219 owned = self.sql(expression, "owned") 1220 owned = f" OWNED BY {owned}" if owned else "" 1221 1222 cache = expression.args.get("cache") 1223 if cache is None: 1224 cache_str = "" 1225 elif cache is True: 1226 cache_str = " CACHE" 1227 else: 1228 cache_str = f" CACHE {cache}" 1229 1230 options = self.expressions(expression, key="options", flat=True, sep=" ") 1231 options = f" {options}" if options else "" 1232 1233 return f"{start}{increment}{minvalue}{maxvalue}{cache_str}{options}{owned}".lstrip()
1235 def clone_sql(self, expression: exp.Clone) -> str: 1236 this = self.sql(expression, "this") 1237 shallow = "SHALLOW " if expression.args.get("shallow") else "" 1238 keyword = "COPY" if expression.args.get("copy") and self.SUPPORTS_TABLE_COPY else "CLONE" 1239 return f"{shallow}{keyword} {this}"
1241 def describe_sql(self, expression: exp.Describe) -> str: 1242 style = expression.args.get("style") 1243 style = f" {style}" if style else "" 1244 partition = self.sql(expression, "partition") 1245 partition = f" {partition}" if partition else "" 1246 format = self.sql(expression, "format") 1247 format = f" {format}" if format else "" 1248 1249 return f"DESCRIBE{style}{format} {self.sql(expression, 'this')}{partition}"
1261 def with_sql(self, expression: exp.With) -> str: 1262 sql = self.expressions(expression, flat=True) 1263 recursive = ( 1264 "RECURSIVE " 1265 if self.CTE_RECURSIVE_KEYWORD_REQUIRED and expression.args.get("recursive") 1266 else "" 1267 ) 1268 search = self.sql(expression, "search") 1269 search = f" {search}" if search else "" 1270 1271 return f"WITH {recursive}{sql}{search}"
1273 def cte_sql(self, expression: exp.CTE) -> str: 1274 alias = expression.args.get("alias") 1275 if alias: 1276 alias.add_comments(expression.pop_comments()) 1277 1278 alias_sql = self.sql(expression, "alias") 1279 1280 materialized = expression.args.get("materialized") 1281 if materialized is False: 1282 materialized = "NOT MATERIALIZED " 1283 elif materialized: 1284 materialized = "MATERIALIZED " 1285 1286 return f"{alias_sql} AS {materialized or ''}{self.wrap(expression)}"
1288 def tablealias_sql(self, expression: exp.TableAlias) -> str: 1289 alias = self.sql(expression, "this") 1290 columns = self.expressions(expression, key="columns", flat=True) 1291 columns = f"({columns})" if columns else "" 1292 1293 if columns and not self.SUPPORTS_TABLE_ALIAS_COLUMNS: 1294 columns = "" 1295 self.unsupported("Named columns are not supported in table alias.") 1296 1297 if not alias and not self.dialect.UNNEST_COLUMN_ONLY: 1298 alias = self._next_name() 1299 1300 return f"{alias}{columns}"
def
hexstring_sql( self, expression: sqlglot.expressions.HexString, binary_function_repr: Optional[str] = None) -> str:
1308 def hexstring_sql( 1309 self, expression: exp.HexString, binary_function_repr: t.Optional[str] = None 1310 ) -> str: 1311 this = self.sql(expression, "this") 1312 is_integer_type = expression.args.get("is_integer") 1313 1314 if (is_integer_type and not self.dialect.HEX_STRING_IS_INTEGER_TYPE) or ( 1315 not self.dialect.HEX_START and not binary_function_repr 1316 ): 1317 # Integer representation will be returned if: 1318 # - The read dialect treats the hex value as integer literal but not the write 1319 # - The transpilation is not supported (write dialect hasn't set HEX_START or the param flag) 1320 return f"{int(this, 16)}" 1321 1322 if not is_integer_type: 1323 # Read dialect treats the hex value as BINARY/BLOB 1324 if binary_function_repr: 1325 # The write dialect supports the transpilation to its equivalent BINARY/BLOB 1326 return self.func(binary_function_repr, exp.Literal.string(this)) 1327 if self.dialect.HEX_STRING_IS_INTEGER_TYPE: 1328 # The write dialect does not support the transpilation, it'll treat the hex value as INTEGER 1329 self.unsupported("Unsupported transpilation from BINARY/BLOB hex string") 1330 1331 return f"{self.dialect.HEX_START}{this}{self.dialect.HEX_END}"
1339 def unicodestring_sql(self, expression: exp.UnicodeString) -> str: 1340 this = self.sql(expression, "this") 1341 escape = expression.args.get("escape") 1342 1343 if self.dialect.UNICODE_START: 1344 escape_substitute = r"\\\1" 1345 left_quote, right_quote = self.dialect.UNICODE_START, self.dialect.UNICODE_END 1346 else: 1347 escape_substitute = r"\\u\1" 1348 left_quote, right_quote = self.dialect.QUOTE_START, self.dialect.QUOTE_END 1349 1350 if escape: 1351 escape_pattern = re.compile(rf"{escape.name}(\d+)") 1352 escape_sql = f" UESCAPE {self.sql(escape)}" if self.SUPPORTS_UESCAPE else "" 1353 else: 1354 escape_pattern = ESCAPED_UNICODE_RE 1355 escape_sql = "" 1356 1357 if not self.dialect.UNICODE_START or (escape and not self.SUPPORTS_UESCAPE): 1358 this = escape_pattern.sub(escape_substitute, this) 1359 1360 return f"{left_quote}{this}{right_quote}{escape_sql}"
1362 def rawstring_sql(self, expression: exp.RawString) -> str: 1363 string = expression.this 1364 if "\\" in self.dialect.tokenizer_class.STRING_ESCAPES: 1365 string = string.replace("\\", "\\\\") 1366 1367 string = self.escape_str(string, escape_backslash=False) 1368 return f"{self.dialect.QUOTE_START}{string}{self.dialect.QUOTE_END}"
1376 def datatype_sql(self, expression: exp.DataType) -> str: 1377 nested = "" 1378 values = "" 1379 interior = self.expressions(expression, flat=True) 1380 1381 type_value = expression.this 1382 if type_value == exp.DataType.Type.USERDEFINED and expression.args.get("kind"): 1383 type_sql = self.sql(expression, "kind") 1384 else: 1385 type_sql = ( 1386 self.TYPE_MAPPING.get(type_value, type_value.value) 1387 if isinstance(type_value, exp.DataType.Type) 1388 else type_value 1389 ) 1390 1391 if interior: 1392 if expression.args.get("nested"): 1393 nested = f"{self.STRUCT_DELIMITER[0]}{interior}{self.STRUCT_DELIMITER[1]}" 1394 if expression.args.get("values") is not None: 1395 delimiters = ("[", "]") if type_value == exp.DataType.Type.ARRAY else ("(", ")") 1396 values = self.expressions(expression, key="values", flat=True) 1397 values = f"{delimiters[0]}{values}{delimiters[1]}" 1398 elif type_value == exp.DataType.Type.INTERVAL: 1399 nested = f" {interior}" 1400 else: 1401 nested = f"({interior})" 1402 1403 type_sql = f"{type_sql}{nested}{values}" 1404 if self.TZ_TO_WITH_TIME_ZONE and type_value in ( 1405 exp.DataType.Type.TIMETZ, 1406 exp.DataType.Type.TIMESTAMPTZ, 1407 ): 1408 type_sql = f"{type_sql} WITH TIME ZONE" 1409 1410 return type_sql
1412 def directory_sql(self, expression: exp.Directory) -> str: 1413 local = "LOCAL " if expression.args.get("local") else "" 1414 row_format = self.sql(expression, "row_format") 1415 row_format = f" {row_format}" if row_format else "" 1416 return f"{local}DIRECTORY {self.sql(expression, 'this')}{row_format}"
1418 def delete_sql(self, expression: exp.Delete) -> str: 1419 this = self.sql(expression, "this") 1420 this = f" FROM {this}" if this else "" 1421 using = self.sql(expression, "using") 1422 using = f" USING {using}" if using else "" 1423 cluster = self.sql(expression, "cluster") 1424 cluster = f" {cluster}" if cluster else "" 1425 where = self.sql(expression, "where") 1426 returning = self.sql(expression, "returning") 1427 limit = self.sql(expression, "limit") 1428 tables = self.expressions(expression, key="tables") 1429 tables = f" {tables}" if tables else "" 1430 if self.RETURNING_END: 1431 expression_sql = f"{this}{using}{cluster}{where}{returning}{limit}" 1432 else: 1433 expression_sql = f"{returning}{this}{using}{cluster}{where}{limit}" 1434 return self.prepend_ctes(expression, f"DELETE{tables}{expression_sql}")
1436 def drop_sql(self, expression: exp.Drop) -> str: 1437 this = self.sql(expression, "this") 1438 expressions = self.expressions(expression, flat=True) 1439 expressions = f" ({expressions})" if expressions else "" 1440 kind = expression.args["kind"] 1441 kind = self.dialect.INVERSE_CREATABLE_KIND_MAPPING.get(kind) or kind 1442 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 1443 concurrently_sql = " CONCURRENTLY" if expression.args.get("concurrently") else "" 1444 on_cluster = self.sql(expression, "cluster") 1445 on_cluster = f" {on_cluster}" if on_cluster else "" 1446 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 1447 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 1448 cascade = " CASCADE" if expression.args.get("cascade") else "" 1449 constraints = " CONSTRAINTS" if expression.args.get("constraints") else "" 1450 purge = " PURGE" if expression.args.get("purge") else "" 1451 return f"DROP{temporary}{materialized} {kind}{concurrently_sql}{exists_sql}{this}{on_cluster}{expressions}{cascade}{constraints}{purge}"
1453 def set_operation(self, expression: exp.SetOperation) -> str: 1454 op_type = type(expression) 1455 op_name = op_type.key.upper() 1456 1457 distinct = expression.args.get("distinct") 1458 if ( 1459 distinct is False 1460 and op_type in (exp.Except, exp.Intersect) 1461 and not self.EXCEPT_INTERSECT_SUPPORT_ALL_CLAUSE 1462 ): 1463 self.unsupported(f"{op_name} ALL is not supported") 1464 1465 default_distinct = self.dialect.SET_OP_DISTINCT_BY_DEFAULT[op_type] 1466 1467 if distinct is None: 1468 distinct = default_distinct 1469 if distinct is None: 1470 self.unsupported(f"{op_name} requires DISTINCT or ALL to be specified") 1471 1472 if distinct is default_distinct: 1473 distinct_or_all = "" 1474 else: 1475 distinct_or_all = " DISTINCT" if distinct else " ALL" 1476 1477 side_kind = " ".join(filter(None, [expression.side, expression.kind])) 1478 side_kind = f"{side_kind} " if side_kind else "" 1479 1480 by_name = " BY NAME" if expression.args.get("by_name") else "" 1481 on = self.expressions(expression, key="on", flat=True) 1482 on = f" ON ({on})" if on else "" 1483 1484 return f"{side_kind}{op_name}{distinct_or_all}{by_name}{on}"
1486 def set_operations(self, expression: exp.SetOperation) -> str: 1487 if not self.SET_OP_MODIFIERS: 1488 limit = expression.args.get("limit") 1489 order = expression.args.get("order") 1490 1491 if limit or order: 1492 select = self._move_ctes_to_top_level( 1493 exp.subquery(expression, "_l_0", copy=False).select("*", copy=False) 1494 ) 1495 1496 if limit: 1497 select = select.limit(limit.pop(), copy=False) 1498 if order: 1499 select = select.order_by(order.pop(), copy=False) 1500 return self.sql(select) 1501 1502 sqls: t.List[str] = [] 1503 stack: t.List[t.Union[str, exp.Expression]] = [expression] 1504 1505 while stack: 1506 node = stack.pop() 1507 1508 if isinstance(node, exp.SetOperation): 1509 stack.append(node.expression) 1510 stack.append( 1511 self.maybe_comment( 1512 self.set_operation(node), comments=node.comments, separated=True 1513 ) 1514 ) 1515 stack.append(node.this) 1516 else: 1517 sqls.append(self.sql(node)) 1518 1519 this = self.sep().join(sqls) 1520 this = self.query_modifiers(expression, this) 1521 return self.prepend_ctes(expression, this)
1523 def fetch_sql(self, expression: exp.Fetch) -> str: 1524 direction = expression.args.get("direction") 1525 direction = f" {direction}" if direction else "" 1526 count = self.sql(expression, "count") 1527 count = f" {count}" if count else "" 1528 limit_options = self.sql(expression, "limit_options") 1529 limit_options = f"{limit_options}" if limit_options else " ROWS ONLY" 1530 return f"{self.seg('FETCH')}{direction}{count}{limit_options}"
1532 def limitoptions_sql(self, expression: exp.LimitOptions) -> str: 1533 percent = " PERCENT" if expression.args.get("percent") else "" 1534 rows = " ROWS" if expression.args.get("rows") else "" 1535 with_ties = " WITH TIES" if expression.args.get("with_ties") else "" 1536 if not with_ties and rows: 1537 with_ties = " ONLY" 1538 return f"{percent}{rows}{with_ties}"
1540 def filter_sql(self, expression: exp.Filter) -> str: 1541 if self.AGGREGATE_FILTER_SUPPORTED: 1542 this = self.sql(expression, "this") 1543 where = self.sql(expression, "expression").strip() 1544 return f"{this} FILTER({where})" 1545 1546 agg = expression.this 1547 agg_arg = agg.this 1548 cond = expression.expression.this 1549 agg_arg.replace(exp.If(this=cond.copy(), true=agg_arg.copy())) 1550 return self.sql(agg)
1559 def indexparameters_sql(self, expression: exp.IndexParameters) -> str: 1560 using = self.sql(expression, "using") 1561 using = f" USING {using}" if using else "" 1562 columns = self.expressions(expression, key="columns", flat=True) 1563 columns = f"({columns})" if columns else "" 1564 partition_by = self.expressions(expression, key="partition_by", flat=True) 1565 partition_by = f" PARTITION BY {partition_by}" if partition_by else "" 1566 where = self.sql(expression, "where") 1567 include = self.expressions(expression, key="include", flat=True) 1568 if include: 1569 include = f" INCLUDE ({include})" 1570 with_storage = self.expressions(expression, key="with_storage", flat=True) 1571 with_storage = f" WITH ({with_storage})" if with_storage else "" 1572 tablespace = self.sql(expression, "tablespace") 1573 tablespace = f" USING INDEX TABLESPACE {tablespace}" if tablespace else "" 1574 on = self.sql(expression, "on") 1575 on = f" ON {on}" if on else "" 1576 1577 return f"{using}{columns}{include}{with_storage}{tablespace}{partition_by}{where}{on}"
1579 def index_sql(self, expression: exp.Index) -> str: 1580 unique = "UNIQUE " if expression.args.get("unique") else "" 1581 primary = "PRIMARY " if expression.args.get("primary") else "" 1582 amp = "AMP " if expression.args.get("amp") else "" 1583 name = self.sql(expression, "this") 1584 name = f"{name} " if name else "" 1585 table = self.sql(expression, "table") 1586 table = f"{self.INDEX_ON} {table}" if table else "" 1587 1588 index = "INDEX " if not table else "" 1589 1590 params = self.sql(expression, "params") 1591 return f"{unique}{primary}{amp}{index}{name}{table}{params}"
1593 def identifier_sql(self, expression: exp.Identifier) -> str: 1594 text = expression.name 1595 lower = text.lower() 1596 text = lower if self.normalize and not expression.quoted else text 1597 text = text.replace(self._identifier_end, self._escaped_identifier_end) 1598 if ( 1599 expression.quoted 1600 or self.dialect.can_identify(text, self.identify) 1601 or lower in self.RESERVED_KEYWORDS 1602 or (not self.dialect.IDENTIFIERS_CAN_START_WITH_DIGIT and text[:1].isdigit()) 1603 ): 1604 text = f"{self._identifier_start}{text}{self._identifier_end}" 1605 return text
1620 def inputoutputformat_sql(self, expression: exp.InputOutputFormat) -> str: 1621 input_format = self.sql(expression, "input_format") 1622 input_format = f"INPUTFORMAT {input_format}" if input_format else "" 1623 output_format = self.sql(expression, "output_format") 1624 output_format = f"OUTPUTFORMAT {output_format}" if output_format else "" 1625 return self.sep().join((input_format, output_format))
1635 def properties_sql(self, expression: exp.Properties) -> str: 1636 root_properties = [] 1637 with_properties = [] 1638 1639 for p in expression.expressions: 1640 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1641 if p_loc == exp.Properties.Location.POST_WITH: 1642 with_properties.append(p) 1643 elif p_loc == exp.Properties.Location.POST_SCHEMA: 1644 root_properties.append(p) 1645 1646 root_props = self.root_properties(exp.Properties(expressions=root_properties)) 1647 with_props = self.with_properties(exp.Properties(expressions=with_properties)) 1648 1649 if root_props and with_props and not self.pretty: 1650 with_props = " " + with_props 1651 1652 return root_props + with_props
def
properties( self, properties: sqlglot.expressions.Properties, prefix: str = '', sep: str = ', ', suffix: str = '', wrapped: bool = True) -> str:
1659 def properties( 1660 self, 1661 properties: exp.Properties, 1662 prefix: str = "", 1663 sep: str = ", ", 1664 suffix: str = "", 1665 wrapped: bool = True, 1666 ) -> str: 1667 if properties.expressions: 1668 expressions = self.expressions(properties, sep=sep, indent=False) 1669 if expressions: 1670 expressions = self.wrap(expressions) if wrapped else expressions 1671 return f"{prefix}{' ' if prefix.strip() else ''}{expressions}{suffix}" 1672 return ""
1677 def locate_properties(self, properties: exp.Properties) -> t.DefaultDict: 1678 properties_locs = defaultdict(list) 1679 for p in properties.expressions: 1680 p_loc = self.PROPERTIES_LOCATION[p.__class__] 1681 if p_loc != exp.Properties.Location.UNSUPPORTED: 1682 properties_locs[p_loc].append(p) 1683 else: 1684 self.unsupported(f"Unsupported property {p.key}") 1685 1686 return properties_locs
def
property_name( self, expression: sqlglot.expressions.Property, string_key: bool = False) -> str:
1693 def property_sql(self, expression: exp.Property) -> str: 1694 property_cls = expression.__class__ 1695 if property_cls == exp.Property: 1696 return f"{self.property_name(expression)}={self.sql(expression, 'value')}" 1697 1698 property_name = exp.Properties.PROPERTY_TO_NAME.get(property_cls) 1699 if not property_name: 1700 self.unsupported(f"Unsupported property {expression.key}") 1701 1702 return f"{property_name}={self.sql(expression, 'this')}"
1704 def likeproperty_sql(self, expression: exp.LikeProperty) -> str: 1705 if self.SUPPORTS_CREATE_TABLE_LIKE: 1706 options = " ".join(f"{e.name} {self.sql(e, 'value')}" for e in expression.expressions) 1707 options = f" {options}" if options else "" 1708 1709 like = f"LIKE {self.sql(expression, 'this')}{options}" 1710 if self.LIKE_PROPERTY_INSIDE_SCHEMA and not isinstance(expression.parent, exp.Schema): 1711 like = f"({like})" 1712 1713 return like 1714 1715 if expression.expressions: 1716 self.unsupported("Transpilation of LIKE property options is unsupported") 1717 1718 select = exp.select("*").from_(expression.this).limit(0) 1719 return f"AS {self.sql(select)}"
1726 def journalproperty_sql(self, expression: exp.JournalProperty) -> str: 1727 no = "NO " if expression.args.get("no") else "" 1728 local = expression.args.get("local") 1729 local = f"{local} " if local else "" 1730 dual = "DUAL " if expression.args.get("dual") else "" 1731 before = "BEFORE " if expression.args.get("before") else "" 1732 after = "AFTER " if expression.args.get("after") else "" 1733 return f"{no}{local}{dual}{before}{after}JOURNAL"
def
mergeblockratioproperty_sql(self, expression: sqlglot.expressions.MergeBlockRatioProperty) -> str:
1749 def mergeblockratioproperty_sql(self, expression: exp.MergeBlockRatioProperty) -> str: 1750 if expression.args.get("no"): 1751 return "NO MERGEBLOCKRATIO" 1752 if expression.args.get("default"): 1753 return "DEFAULT MERGEBLOCKRATIO" 1754 1755 percent = " PERCENT" if expression.args.get("percent") else "" 1756 return f"MERGEBLOCKRATIO={self.sql(expression, 'this')}{percent}"
1758 def datablocksizeproperty_sql(self, expression: exp.DataBlocksizeProperty) -> str: 1759 default = expression.args.get("default") 1760 minimum = expression.args.get("minimum") 1761 maximum = expression.args.get("maximum") 1762 if default or minimum or maximum: 1763 if default: 1764 prop = "DEFAULT" 1765 elif minimum: 1766 prop = "MINIMUM" 1767 else: 1768 prop = "MAXIMUM" 1769 return f"{prop} DATABLOCKSIZE" 1770 units = expression.args.get("units") 1771 units = f" {units}" if units else "" 1772 return f"DATABLOCKSIZE={self.sql(expression, 'size')}{units}"
def
blockcompressionproperty_sql(self, expression: sqlglot.expressions.BlockCompressionProperty) -> str:
1774 def blockcompressionproperty_sql(self, expression: exp.BlockCompressionProperty) -> str: 1775 autotemp = expression.args.get("autotemp") 1776 always = expression.args.get("always") 1777 default = expression.args.get("default") 1778 manual = expression.args.get("manual") 1779 never = expression.args.get("never") 1780 1781 if autotemp is not None: 1782 prop = f"AUTOTEMP({self.expressions(autotemp)})" 1783 elif always: 1784 prop = "ALWAYS" 1785 elif default: 1786 prop = "DEFAULT" 1787 elif manual: 1788 prop = "MANUAL" 1789 elif never: 1790 prop = "NEVER" 1791 return f"BLOCKCOMPRESSION={prop}"
def
isolatedloadingproperty_sql(self, expression: sqlglot.expressions.IsolatedLoadingProperty) -> str:
1793 def isolatedloadingproperty_sql(self, expression: exp.IsolatedLoadingProperty) -> str: 1794 no = expression.args.get("no") 1795 no = " NO" if no else "" 1796 concurrent = expression.args.get("concurrent") 1797 concurrent = " CONCURRENT" if concurrent else "" 1798 target = self.sql(expression, "target") 1799 target = f" {target}" if target else "" 1800 return f"WITH{no}{concurrent} ISOLATED LOADING{target}"
1802 def partitionboundspec_sql(self, expression: exp.PartitionBoundSpec) -> str: 1803 if isinstance(expression.this, list): 1804 return f"IN ({self.expressions(expression, key='this', flat=True)})" 1805 if expression.this: 1806 modulus = self.sql(expression, "this") 1807 remainder = self.sql(expression, "expression") 1808 return f"WITH (MODULUS {modulus}, REMAINDER {remainder})" 1809 1810 from_expressions = self.expressions(expression, key="from_expressions", flat=True) 1811 to_expressions = self.expressions(expression, key="to_expressions", flat=True) 1812 return f"FROM ({from_expressions}) TO ({to_expressions})"
1814 def partitionedofproperty_sql(self, expression: exp.PartitionedOfProperty) -> str: 1815 this = self.sql(expression, "this") 1816 1817 for_values_or_default = expression.expression 1818 if isinstance(for_values_or_default, exp.PartitionBoundSpec): 1819 for_values_or_default = f" FOR VALUES {self.sql(for_values_or_default)}" 1820 else: 1821 for_values_or_default = " DEFAULT" 1822 1823 return f"PARTITION OF {this}{for_values_or_default}"
1825 def lockingproperty_sql(self, expression: exp.LockingProperty) -> str: 1826 kind = expression.args.get("kind") 1827 this = f" {self.sql(expression, 'this')}" if expression.this else "" 1828 for_or_in = expression.args.get("for_or_in") 1829 for_or_in = f" {for_or_in}" if for_or_in else "" 1830 lock_type = expression.args.get("lock_type") 1831 override = " OVERRIDE" if expression.args.get("override") else "" 1832 return f"LOCKING {kind}{this}{for_or_in} {lock_type}{override}"
1834 def withdataproperty_sql(self, expression: exp.WithDataProperty) -> str: 1835 data_sql = f"WITH {'NO ' if expression.args.get('no') else ''}DATA" 1836 statistics = expression.args.get("statistics") 1837 statistics_sql = "" 1838 if statistics is not None: 1839 statistics_sql = f" AND {'NO ' if not statistics else ''}STATISTICS" 1840 return f"{data_sql}{statistics_sql}"
def
withsystemversioningproperty_sql( self, expression: sqlglot.expressions.WithSystemVersioningProperty) -> str:
1842 def withsystemversioningproperty_sql(self, expression: exp.WithSystemVersioningProperty) -> str: 1843 this = self.sql(expression, "this") 1844 this = f"HISTORY_TABLE={this}" if this else "" 1845 data_consistency: t.Optional[str] = self.sql(expression, "data_consistency") 1846 data_consistency = ( 1847 f"DATA_CONSISTENCY_CHECK={data_consistency}" if data_consistency else None 1848 ) 1849 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 1850 retention_period = ( 1851 f"HISTORY_RETENTION_PERIOD={retention_period}" if retention_period else None 1852 ) 1853 1854 if this: 1855 on_sql = self.func("ON", this, data_consistency, retention_period) 1856 else: 1857 on_sql = "ON" if expression.args.get("on") else "OFF" 1858 1859 sql = f"SYSTEM_VERSIONING={on_sql}" 1860 1861 return f"WITH({sql})" if expression.args.get("with") else sql
1863 def insert_sql(self, expression: exp.Insert) -> str: 1864 hint = self.sql(expression, "hint") 1865 overwrite = expression.args.get("overwrite") 1866 1867 if isinstance(expression.this, exp.Directory): 1868 this = " OVERWRITE" if overwrite else " INTO" 1869 else: 1870 this = self.INSERT_OVERWRITE if overwrite else " INTO" 1871 1872 stored = self.sql(expression, "stored") 1873 stored = f" {stored}" if stored else "" 1874 alternative = expression.args.get("alternative") 1875 alternative = f" OR {alternative}" if alternative else "" 1876 ignore = " IGNORE" if expression.args.get("ignore") else "" 1877 is_function = expression.args.get("is_function") 1878 if is_function: 1879 this = f"{this} FUNCTION" 1880 this = f"{this} {self.sql(expression, 'this')}" 1881 1882 exists = " IF EXISTS" if expression.args.get("exists") else "" 1883 where = self.sql(expression, "where") 1884 where = f"{self.sep()}REPLACE WHERE {where}" if where else "" 1885 expression_sql = f"{self.sep()}{self.sql(expression, 'expression')}" 1886 on_conflict = self.sql(expression, "conflict") 1887 on_conflict = f" {on_conflict}" if on_conflict else "" 1888 by_name = " BY NAME" if expression.args.get("by_name") else "" 1889 returning = self.sql(expression, "returning") 1890 1891 if self.RETURNING_END: 1892 expression_sql = f"{expression_sql}{on_conflict}{returning}" 1893 else: 1894 expression_sql = f"{returning}{expression_sql}{on_conflict}" 1895 1896 partition_by = self.sql(expression, "partition") 1897 partition_by = f" {partition_by}" if partition_by else "" 1898 settings = self.sql(expression, "settings") 1899 settings = f" {settings}" if settings else "" 1900 1901 source = self.sql(expression, "source") 1902 source = f"TABLE {source}" if source else "" 1903 1904 sql = f"INSERT{hint}{alternative}{ignore}{this}{stored}{by_name}{exists}{partition_by}{settings}{where}{expression_sql}{source}" 1905 return self.prepend_ctes(expression, sql)
1923 def onconflict_sql(self, expression: exp.OnConflict) -> str: 1924 conflict = "ON DUPLICATE KEY" if expression.args.get("duplicate") else "ON CONFLICT" 1925 1926 constraint = self.sql(expression, "constraint") 1927 constraint = f" ON CONSTRAINT {constraint}" if constraint else "" 1928 1929 conflict_keys = self.expressions(expression, key="conflict_keys", flat=True) 1930 conflict_keys = f"({conflict_keys}) " if conflict_keys else " " 1931 action = self.sql(expression, "action") 1932 1933 expressions = self.expressions(expression, flat=True) 1934 if expressions: 1935 set_keyword = "SET " if self.DUPLICATE_KEY_UPDATE_WITH_SET else "" 1936 expressions = f" {set_keyword}{expressions}" 1937 1938 where = self.sql(expression, "where") 1939 return f"{conflict}{constraint}{conflict_keys}{action}{expressions}{where}"
def
rowformatdelimitedproperty_sql(self, expression: sqlglot.expressions.RowFormatDelimitedProperty) -> str:
1944 def rowformatdelimitedproperty_sql(self, expression: exp.RowFormatDelimitedProperty) -> str: 1945 fields = self.sql(expression, "fields") 1946 fields = f" FIELDS TERMINATED BY {fields}" if fields else "" 1947 escaped = self.sql(expression, "escaped") 1948 escaped = f" ESCAPED BY {escaped}" if escaped else "" 1949 items = self.sql(expression, "collection_items") 1950 items = f" COLLECTION ITEMS TERMINATED BY {items}" if items else "" 1951 keys = self.sql(expression, "map_keys") 1952 keys = f" MAP KEYS TERMINATED BY {keys}" if keys else "" 1953 lines = self.sql(expression, "lines") 1954 lines = f" LINES TERMINATED BY {lines}" if lines else "" 1955 null = self.sql(expression, "null") 1956 null = f" NULL DEFINED AS {null}" if null else "" 1957 return f"ROW FORMAT DELIMITED{fields}{escaped}{items}{keys}{lines}{null}"
1985 def table_sql(self, expression: exp.Table, sep: str = " AS ") -> str: 1986 table = self.table_parts(expression) 1987 only = "ONLY " if expression.args.get("only") else "" 1988 partition = self.sql(expression, "partition") 1989 partition = f" {partition}" if partition else "" 1990 version = self.sql(expression, "version") 1991 version = f" {version}" if version else "" 1992 alias = self.sql(expression, "alias") 1993 alias = f"{sep}{alias}" if alias else "" 1994 1995 sample = self.sql(expression, "sample") 1996 if self.dialect.ALIAS_POST_TABLESAMPLE: 1997 sample_pre_alias = sample 1998 sample_post_alias = "" 1999 else: 2000 sample_pre_alias = "" 2001 sample_post_alias = sample 2002 2003 hints = self.expressions(expression, key="hints", sep=" ") 2004 hints = f" {hints}" if hints and self.TABLE_HINTS else "" 2005 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2006 joins = self.indent( 2007 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2008 ) 2009 laterals = self.expressions(expression, key="laterals", sep="") 2010 2011 file_format = self.sql(expression, "format") 2012 if file_format: 2013 pattern = self.sql(expression, "pattern") 2014 pattern = f", PATTERN => {pattern}" if pattern else "" 2015 file_format = f" (FILE_FORMAT => {file_format}{pattern})" 2016 2017 ordinality = expression.args.get("ordinality") or "" 2018 if ordinality: 2019 ordinality = f" WITH ORDINALITY{alias}" 2020 alias = "" 2021 2022 when = self.sql(expression, "when") 2023 if when: 2024 table = f"{table} {when}" 2025 2026 changes = self.sql(expression, "changes") 2027 changes = f" {changes}" if changes else "" 2028 2029 rows_from = self.expressions(expression, key="rows_from") 2030 if rows_from: 2031 table = f"ROWS FROM {self.wrap(rows_from)}" 2032 2033 return f"{only}{table}{changes}{partition}{version}{file_format}{sample_pre_alias}{alias}{hints}{pivots}{sample_post_alias}{joins}{laterals}{ordinality}"
2035 def tablefromrows_sql(self, expression: exp.TableFromRows) -> str: 2036 table = self.func("TABLE", expression.this) 2037 alias = self.sql(expression, "alias") 2038 alias = f" AS {alias}" if alias else "" 2039 sample = self.sql(expression, "sample") 2040 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2041 joins = self.indent( 2042 self.expressions(expression, key="joins", sep="", flat=True), skip_first=True 2043 ) 2044 return f"{table}{alias}{pivots}{sample}{joins}"
def
tablesample_sql( self, expression: sqlglot.expressions.TableSample, tablesample_keyword: Optional[str] = None) -> str:
2046 def tablesample_sql( 2047 self, 2048 expression: exp.TableSample, 2049 tablesample_keyword: t.Optional[str] = None, 2050 ) -> str: 2051 method = self.sql(expression, "method") 2052 method = f"{method} " if method and self.TABLESAMPLE_WITH_METHOD else "" 2053 numerator = self.sql(expression, "bucket_numerator") 2054 denominator = self.sql(expression, "bucket_denominator") 2055 field = self.sql(expression, "bucket_field") 2056 field = f" ON {field}" if field else "" 2057 bucket = f"BUCKET {numerator} OUT OF {denominator}{field}" if numerator else "" 2058 seed = self.sql(expression, "seed") 2059 seed = f" {self.TABLESAMPLE_SEED_KEYWORD} ({seed})" if seed else "" 2060 2061 size = self.sql(expression, "size") 2062 if size and self.TABLESAMPLE_SIZE_IS_ROWS: 2063 size = f"{size} ROWS" 2064 2065 percent = self.sql(expression, "percent") 2066 if percent and not self.dialect.TABLESAMPLE_SIZE_IS_PERCENT: 2067 percent = f"{percent} PERCENT" 2068 2069 expr = f"{bucket}{percent}{size}" 2070 if self.TABLESAMPLE_REQUIRES_PARENS: 2071 expr = f"({expr})" 2072 2073 return f" {tablesample_keyword or self.TABLESAMPLE_KEYWORDS} {method}{expr}{seed}"
2075 def pivot_sql(self, expression: exp.Pivot) -> str: 2076 expressions = self.expressions(expression, flat=True) 2077 direction = "UNPIVOT" if expression.unpivot else "PIVOT" 2078 2079 group = self.sql(expression, "group") 2080 2081 if expression.this: 2082 this = self.sql(expression, "this") 2083 if not expressions: 2084 return f"UNPIVOT {this}" 2085 2086 on = f"{self.seg('ON')} {expressions}" 2087 into = self.sql(expression, "into") 2088 into = f"{self.seg('INTO')} {into}" if into else "" 2089 using = self.expressions(expression, key="using", flat=True) 2090 using = f"{self.seg('USING')} {using}" if using else "" 2091 return f"{direction} {this}{on}{into}{using}{group}" 2092 2093 alias = self.sql(expression, "alias") 2094 alias = f" AS {alias}" if alias else "" 2095 2096 fields = self.expressions( 2097 expression, 2098 "fields", 2099 sep=" ", 2100 dynamic=True, 2101 new_line=True, 2102 skip_first=True, 2103 skip_last=True, 2104 ) 2105 2106 include_nulls = expression.args.get("include_nulls") 2107 if include_nulls is not None: 2108 nulls = " INCLUDE NULLS " if include_nulls else " EXCLUDE NULLS " 2109 else: 2110 nulls = "" 2111 2112 default_on_null = self.sql(expression, "default_on_null") 2113 default_on_null = f" DEFAULT ON NULL ({default_on_null})" if default_on_null else "" 2114 return f"{self.seg(direction)}{nulls}({expressions} FOR {fields}{default_on_null}{group}){alias}"
2125 def update_sql(self, expression: exp.Update) -> str: 2126 this = self.sql(expression, "this") 2127 set_sql = self.expressions(expression, flat=True) 2128 from_sql = self.sql(expression, "from") 2129 where_sql = self.sql(expression, "where") 2130 returning = self.sql(expression, "returning") 2131 order = self.sql(expression, "order") 2132 limit = self.sql(expression, "limit") 2133 if self.RETURNING_END: 2134 expression_sql = f"{from_sql}{where_sql}{returning}" 2135 else: 2136 expression_sql = f"{returning}{from_sql}{where_sql}" 2137 sql = f"UPDATE {this} SET {set_sql}{expression_sql}{order}{limit}" 2138 return self.prepend_ctes(expression, sql)
2140 def values_sql(self, expression: exp.Values, values_as_table: bool = True) -> str: 2141 values_as_table = values_as_table and self.VALUES_AS_TABLE 2142 2143 # The VALUES clause is still valid in an `INSERT INTO ..` statement, for example 2144 if values_as_table or not expression.find_ancestor(exp.From, exp.Join): 2145 args = self.expressions(expression) 2146 alias = self.sql(expression, "alias") 2147 values = f"VALUES{self.seg('')}{args}" 2148 values = ( 2149 f"({values})" 2150 if self.WRAP_DERIVED_VALUES 2151 and (alias or isinstance(expression.parent, (exp.From, exp.Table))) 2152 else values 2153 ) 2154 return f"{values} AS {alias}" if alias else values 2155 2156 # Converts `VALUES...` expression into a series of select unions. 2157 alias_node = expression.args.get("alias") 2158 column_names = alias_node and alias_node.columns 2159 2160 selects: t.List[exp.Query] = [] 2161 2162 for i, tup in enumerate(expression.expressions): 2163 row = tup.expressions 2164 2165 if i == 0 and column_names: 2166 row = [ 2167 exp.alias_(value, column_name) for value, column_name in zip(row, column_names) 2168 ] 2169 2170 selects.append(exp.Select(expressions=row)) 2171 2172 if self.pretty: 2173 # This may result in poor performance for large-cardinality `VALUES` tables, due to 2174 # the deep nesting of the resulting exp.Unions. If this is a problem, either increase 2175 # `sys.setrecursionlimit` to avoid RecursionErrors, or don't set `pretty`. 2176 query = reduce(lambda x, y: exp.union(x, y, distinct=False, copy=False), selects) 2177 return self.subquery_sql(query.subquery(alias_node and alias_node.this, copy=False)) 2178 2179 alias = f" AS {self.sql(alias_node, 'this')}" if alias_node else "" 2180 unions = " UNION ALL ".join(self.sql(select) for select in selects) 2181 return f"({unions}){alias}"
2186 @unsupported_args("expressions") 2187 def into_sql(self, expression: exp.Into) -> str: 2188 temporary = " TEMPORARY" if expression.args.get("temporary") else "" 2189 unlogged = " UNLOGGED" if expression.args.get("unlogged") else "" 2190 return f"{self.seg('INTO')}{temporary or unlogged} {self.sql(expression, 'this')}"
2207 def group_sql(self, expression: exp.Group) -> str: 2208 group_by_all = expression.args.get("all") 2209 if group_by_all is True: 2210 modifier = " ALL" 2211 elif group_by_all is False: 2212 modifier = " DISTINCT" 2213 else: 2214 modifier = "" 2215 2216 group_by = self.op_expressions(f"GROUP BY{modifier}", expression) 2217 2218 grouping_sets = self.expressions(expression, key="grouping_sets") 2219 cube = self.expressions(expression, key="cube") 2220 rollup = self.expressions(expression, key="rollup") 2221 2222 groupings = csv( 2223 self.seg(grouping_sets) if grouping_sets else "", 2224 self.seg(cube) if cube else "", 2225 self.seg(rollup) if rollup else "", 2226 self.seg("WITH TOTALS") if expression.args.get("totals") else "", 2227 sep=self.GROUPINGS_SEP, 2228 ) 2229 2230 if ( 2231 expression.expressions 2232 and groupings 2233 and groupings.strip() not in ("WITH CUBE", "WITH ROLLUP") 2234 ): 2235 group_by = f"{group_by}{self.GROUPINGS_SEP}" 2236 2237 return f"{group_by}{groupings}"
2243 def connect_sql(self, expression: exp.Connect) -> str: 2244 start = self.sql(expression, "start") 2245 start = self.seg(f"START WITH {start}") if start else "" 2246 nocycle = " NOCYCLE" if expression.args.get("nocycle") else "" 2247 connect = self.sql(expression, "connect") 2248 connect = self.seg(f"CONNECT BY{nocycle} {connect}") 2249 return start + connect
2254 def join_sql(self, expression: exp.Join) -> str: 2255 if not self.SEMI_ANTI_JOIN_WITH_SIDE and expression.kind in ("SEMI", "ANTI"): 2256 side = None 2257 else: 2258 side = expression.side 2259 2260 op_sql = " ".join( 2261 op 2262 for op in ( 2263 expression.method, 2264 "GLOBAL" if expression.args.get("global") else None, 2265 side, 2266 expression.kind, 2267 expression.hint if self.JOIN_HINTS else None, 2268 ) 2269 if op 2270 ) 2271 match_cond = self.sql(expression, "match_condition") 2272 match_cond = f" MATCH_CONDITION ({match_cond})" if match_cond else "" 2273 on_sql = self.sql(expression, "on") 2274 using = expression.args.get("using") 2275 2276 if not on_sql and using: 2277 on_sql = csv(*(self.sql(column) for column in using)) 2278 2279 this = expression.this 2280 this_sql = self.sql(this) 2281 2282 exprs = self.expressions(expression) 2283 if exprs: 2284 this_sql = f"{this_sql},{self.seg(exprs)}" 2285 2286 if on_sql: 2287 on_sql = self.indent(on_sql, skip_first=True) 2288 space = self.seg(" " * self.pad) if self.pretty else " " 2289 if using: 2290 on_sql = f"{space}USING ({on_sql})" 2291 else: 2292 on_sql = f"{space}ON {on_sql}" 2293 elif not op_sql: 2294 if isinstance(this, exp.Lateral) and this.args.get("cross_apply") is not None: 2295 return f" {this_sql}" 2296 2297 return f", {this_sql}" 2298 2299 if op_sql != "STRAIGHT_JOIN": 2300 op_sql = f"{op_sql} JOIN" if op_sql else "JOIN" 2301 2302 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2303 return f"{self.seg(op_sql)} {this_sql}{match_cond}{on_sql}{pivots}"
2310 def lateral_op(self, expression: exp.Lateral) -> str: 2311 cross_apply = expression.args.get("cross_apply") 2312 2313 # https://www.mssqltips.com/sqlservertip/1958/sql-server-cross-apply-and-outer-apply/ 2314 if cross_apply is True: 2315 op = "INNER JOIN " 2316 elif cross_apply is False: 2317 op = "LEFT JOIN " 2318 else: 2319 op = "" 2320 2321 return f"{op}LATERAL"
2323 def lateral_sql(self, expression: exp.Lateral) -> str: 2324 this = self.sql(expression, "this") 2325 2326 if expression.args.get("view"): 2327 alias = expression.args["alias"] 2328 columns = self.expressions(alias, key="columns", flat=True) 2329 table = f" {alias.name}" if alias.name else "" 2330 columns = f" AS {columns}" if columns else "" 2331 op_sql = self.seg(f"LATERAL VIEW{' OUTER' if expression.args.get('outer') else ''}") 2332 return f"{op_sql}{self.sep()}{this}{table}{columns}" 2333 2334 alias = self.sql(expression, "alias") 2335 alias = f" AS {alias}" if alias else "" 2336 2337 ordinality = expression.args.get("ordinality") or "" 2338 if ordinality: 2339 ordinality = f" WITH ORDINALITY{alias}" 2340 alias = "" 2341 2342 return f"{self.lateral_op(expression)} {this}{alias}{ordinality}"
2344 def limit_sql(self, expression: exp.Limit, top: bool = False) -> str: 2345 this = self.sql(expression, "this") 2346 2347 args = [ 2348 self._simplify_unless_literal(e) if self.LIMIT_ONLY_LITERALS else e 2349 for e in (expression.args.get(k) for k in ("offset", "expression")) 2350 if e 2351 ] 2352 2353 args_sql = ", ".join(self.sql(e) for e in args) 2354 args_sql = f"({args_sql})" if top and any(not e.is_number for e in args) else args_sql 2355 expressions = self.expressions(expression, flat=True) 2356 limit_options = self.sql(expression, "limit_options") 2357 expressions = f" BY {expressions}" if expressions else "" 2358 2359 return f"{this}{self.seg('TOP' if top else 'LIMIT')} {args_sql}{limit_options}{expressions}"
2361 def offset_sql(self, expression: exp.Offset) -> str: 2362 this = self.sql(expression, "this") 2363 value = expression.expression 2364 value = self._simplify_unless_literal(value) if self.LIMIT_ONLY_LITERALS else value 2365 expressions = self.expressions(expression, flat=True) 2366 expressions = f" BY {expressions}" if expressions else "" 2367 return f"{this}{self.seg('OFFSET')} {self.sql(value)}{expressions}"
2369 def setitem_sql(self, expression: exp.SetItem) -> str: 2370 kind = self.sql(expression, "kind") 2371 kind = f"{kind} " if kind else "" 2372 this = self.sql(expression, "this") 2373 expressions = self.expressions(expression) 2374 collate = self.sql(expression, "collate") 2375 collate = f" COLLATE {collate}" if collate else "" 2376 global_ = "GLOBAL " if expression.args.get("global") else "" 2377 return f"{global_}{kind}{this}{expressions}{collate}"
2387 def lock_sql(self, expression: exp.Lock) -> str: 2388 if not self.LOCKING_READS_SUPPORTED: 2389 self.unsupported("Locking reads using 'FOR UPDATE/SHARE' are not supported") 2390 return "" 2391 2392 lock_type = "FOR UPDATE" if expression.args["update"] else "FOR SHARE" 2393 expressions = self.expressions(expression, flat=True) 2394 expressions = f" OF {expressions}" if expressions else "" 2395 wait = expression.args.get("wait") 2396 2397 if wait is not None: 2398 if isinstance(wait, exp.Literal): 2399 wait = f" WAIT {self.sql(wait)}" 2400 else: 2401 wait = " NOWAIT" if wait else " SKIP LOCKED" 2402 2403 return f"{lock_type}{expressions}{wait or ''}"
def
escape_str(self, text: str, escape_backslash: bool = True) -> str:
2411 def escape_str(self, text: str, escape_backslash: bool = True) -> str: 2412 if self.dialect.ESCAPED_SEQUENCES: 2413 to_escaped = self.dialect.ESCAPED_SEQUENCES 2414 text = "".join( 2415 to_escaped.get(ch, ch) if escape_backslash or ch != "\\" else ch for ch in text 2416 ) 2417 2418 return self._replace_line_breaks(text).replace( 2419 self.dialect.QUOTE_END, self._escaped_quote_end 2420 )
2422 def loaddata_sql(self, expression: exp.LoadData) -> str: 2423 local = " LOCAL" if expression.args.get("local") else "" 2424 inpath = f" INPATH {self.sql(expression, 'inpath')}" 2425 overwrite = " OVERWRITE" if expression.args.get("overwrite") else "" 2426 this = f" INTO TABLE {self.sql(expression, 'this')}" 2427 partition = self.sql(expression, "partition") 2428 partition = f" {partition}" if partition else "" 2429 input_format = self.sql(expression, "input_format") 2430 input_format = f" INPUTFORMAT {input_format}" if input_format else "" 2431 serde = self.sql(expression, "serde") 2432 serde = f" SERDE {serde}" if serde else "" 2433 return f"LOAD DATA{local}{inpath}{overwrite}{this}{partition}{input_format}{serde}"
2441 def order_sql(self, expression: exp.Order, flat: bool = False) -> str: 2442 this = self.sql(expression, "this") 2443 this = f"{this} " if this else this 2444 siblings = "SIBLINGS " if expression.args.get("siblings") else "" 2445 return self.op_expressions(f"{this}ORDER {siblings}BY", expression, flat=this or flat) # type: ignore
2447 def withfill_sql(self, expression: exp.WithFill) -> str: 2448 from_sql = self.sql(expression, "from") 2449 from_sql = f" FROM {from_sql}" if from_sql else "" 2450 to_sql = self.sql(expression, "to") 2451 to_sql = f" TO {to_sql}" if to_sql else "" 2452 step_sql = self.sql(expression, "step") 2453 step_sql = f" STEP {step_sql}" if step_sql else "" 2454 interpolated_values = [ 2455 f"{self.sql(e, 'alias')} AS {self.sql(e, 'this')}" 2456 if isinstance(e, exp.Alias) 2457 else self.sql(e, "this") 2458 for e in expression.args.get("interpolate") or [] 2459 ] 2460 interpolate = ( 2461 f" INTERPOLATE ({', '.join(interpolated_values)})" if interpolated_values else "" 2462 ) 2463 return f"WITH FILL{from_sql}{to_sql}{step_sql}{interpolate}"
2474 def ordered_sql(self, expression: exp.Ordered) -> str: 2475 desc = expression.args.get("desc") 2476 asc = not desc 2477 2478 nulls_first = expression.args.get("nulls_first") 2479 nulls_last = not nulls_first 2480 nulls_are_large = self.dialect.NULL_ORDERING == "nulls_are_large" 2481 nulls_are_small = self.dialect.NULL_ORDERING == "nulls_are_small" 2482 nulls_are_last = self.dialect.NULL_ORDERING == "nulls_are_last" 2483 2484 this = self.sql(expression, "this") 2485 2486 sort_order = " DESC" if desc else (" ASC" if desc is False else "") 2487 nulls_sort_change = "" 2488 if nulls_first and ( 2489 (asc and nulls_are_large) or (desc and nulls_are_small) or nulls_are_last 2490 ): 2491 nulls_sort_change = " NULLS FIRST" 2492 elif ( 2493 nulls_last 2494 and ((asc and nulls_are_small) or (desc and nulls_are_large)) 2495 and not nulls_are_last 2496 ): 2497 nulls_sort_change = " NULLS LAST" 2498 2499 # If the NULLS FIRST/LAST clause is unsupported, we add another sort key to simulate it 2500 if nulls_sort_change and not self.NULL_ORDERING_SUPPORTED: 2501 window = expression.find_ancestor(exp.Window, exp.Select) 2502 if isinstance(window, exp.Window) and window.args.get("spec"): 2503 self.unsupported( 2504 f"'{nulls_sort_change.strip()}' translation not supported in window functions" 2505 ) 2506 nulls_sort_change = "" 2507 elif self.NULL_ORDERING_SUPPORTED is False and ( 2508 (asc and nulls_sort_change == " NULLS LAST") 2509 or (desc and nulls_sort_change == " NULLS FIRST") 2510 ): 2511 # BigQuery does not allow these ordering/nulls combinations when used under 2512 # an aggregation func or under a window containing one 2513 ancestor = expression.find_ancestor(exp.AggFunc, exp.Window, exp.Select) 2514 2515 if isinstance(ancestor, exp.Window): 2516 ancestor = ancestor.this 2517 if isinstance(ancestor, exp.AggFunc): 2518 self.unsupported( 2519 f"'{nulls_sort_change.strip()}' translation not supported for aggregate functions with {sort_order} sort order" 2520 ) 2521 nulls_sort_change = "" 2522 elif self.NULL_ORDERING_SUPPORTED is None: 2523 if expression.this.is_int: 2524 self.unsupported( 2525 f"'{nulls_sort_change.strip()}' translation not supported with positional ordering" 2526 ) 2527 elif not isinstance(expression.this, exp.Rand): 2528 null_sort_order = " DESC" if nulls_sort_change == " NULLS FIRST" else "" 2529 this = f"CASE WHEN {this} IS NULL THEN 1 ELSE 0 END{null_sort_order}, {this}" 2530 nulls_sort_change = "" 2531 2532 with_fill = self.sql(expression, "with_fill") 2533 with_fill = f" {with_fill}" if with_fill else "" 2534 2535 return f"{this}{sort_order}{nulls_sort_change}{with_fill}"
2545 def matchrecognize_sql(self, expression: exp.MatchRecognize) -> str: 2546 partition = self.partition_by_sql(expression) 2547 order = self.sql(expression, "order") 2548 measures = self.expressions(expression, key="measures") 2549 measures = self.seg(f"MEASURES{self.seg(measures)}") if measures else "" 2550 rows = self.sql(expression, "rows") 2551 rows = self.seg(rows) if rows else "" 2552 after = self.sql(expression, "after") 2553 after = self.seg(after) if after else "" 2554 pattern = self.sql(expression, "pattern") 2555 pattern = self.seg(f"PATTERN ({pattern})") if pattern else "" 2556 definition_sqls = [ 2557 f"{self.sql(definition, 'alias')} AS {self.sql(definition, 'this')}" 2558 for definition in expression.args.get("define", []) 2559 ] 2560 definitions = self.expressions(sqls=definition_sqls) 2561 define = self.seg(f"DEFINE{self.seg(definitions)}") if definitions else "" 2562 body = "".join( 2563 ( 2564 partition, 2565 order, 2566 measures, 2567 rows, 2568 after, 2569 pattern, 2570 define, 2571 ) 2572 ) 2573 alias = self.sql(expression, "alias") 2574 alias = f" {alias}" if alias else "" 2575 return f"{self.seg('MATCH_RECOGNIZE')} {self.wrap(body)}{alias}"
2577 def query_modifiers(self, expression: exp.Expression, *sqls: str) -> str: 2578 limit = expression.args.get("limit") 2579 2580 if self.LIMIT_FETCH == "LIMIT" and isinstance(limit, exp.Fetch): 2581 limit = exp.Limit(expression=exp.maybe_copy(limit.args.get("count"))) 2582 elif self.LIMIT_FETCH == "FETCH" and isinstance(limit, exp.Limit): 2583 limit = exp.Fetch(direction="FIRST", count=exp.maybe_copy(limit.expression)) 2584 2585 return csv( 2586 *sqls, 2587 *[self.sql(join) for join in expression.args.get("joins") or []], 2588 self.sql(expression, "match"), 2589 *[self.sql(lateral) for lateral in expression.args.get("laterals") or []], 2590 self.sql(expression, "prewhere"), 2591 self.sql(expression, "where"), 2592 self.sql(expression, "connect"), 2593 self.sql(expression, "group"), 2594 self.sql(expression, "having"), 2595 *[gen(self, expression) for gen in self.AFTER_HAVING_MODIFIER_TRANSFORMS.values()], 2596 self.sql(expression, "order"), 2597 *self.offset_limit_modifiers(expression, isinstance(limit, exp.Fetch), limit), 2598 *self.after_limit_modifiers(expression), 2599 self.options_modifier(expression), 2600 sep="", 2601 )
def
offset_limit_modifiers( self, expression: sqlglot.expressions.Expression, fetch: bool, limit: Union[sqlglot.expressions.Fetch, sqlglot.expressions.Limit, NoneType]) -> List[str]:
2611 def offset_limit_modifiers( 2612 self, expression: exp.Expression, fetch: bool, limit: t.Optional[exp.Fetch | exp.Limit] 2613 ) -> t.List[str]: 2614 return [ 2615 self.sql(expression, "offset") if fetch else self.sql(limit), 2616 self.sql(limit) if fetch else self.sql(expression, "offset"), 2617 ]
2624 def select_sql(self, expression: exp.Select) -> str: 2625 into = expression.args.get("into") 2626 if not self.SUPPORTS_SELECT_INTO and into: 2627 into.pop() 2628 2629 hint = self.sql(expression, "hint") 2630 distinct = self.sql(expression, "distinct") 2631 distinct = f" {distinct}" if distinct else "" 2632 kind = self.sql(expression, "kind") 2633 2634 limit = expression.args.get("limit") 2635 if isinstance(limit, exp.Limit) and self.LIMIT_IS_TOP: 2636 top = self.limit_sql(limit, top=True) 2637 limit.pop() 2638 else: 2639 top = "" 2640 2641 expressions = self.expressions(expression) 2642 2643 if kind: 2644 if kind in self.SELECT_KINDS: 2645 kind = f" AS {kind}" 2646 else: 2647 if kind == "STRUCT": 2648 expressions = self.expressions( 2649 sqls=[ 2650 self.sql( 2651 exp.Struct( 2652 expressions=[ 2653 exp.PropertyEQ(this=e.args.get("alias"), expression=e.this) 2654 if isinstance(e, exp.Alias) 2655 else e 2656 for e in expression.expressions 2657 ] 2658 ) 2659 ) 2660 ] 2661 ) 2662 kind = "" 2663 2664 operation_modifiers = self.expressions(expression, key="operation_modifiers", sep=" ") 2665 operation_modifiers = f"{self.sep()}{operation_modifiers}" if operation_modifiers else "" 2666 2667 # We use LIMIT_IS_TOP as a proxy for whether DISTINCT should go first because tsql and Teradata 2668 # are the only dialects that use LIMIT_IS_TOP and both place DISTINCT first. 2669 top_distinct = f"{distinct}{hint}{top}" if self.LIMIT_IS_TOP else f"{top}{hint}{distinct}" 2670 expressions = f"{self.sep()}{expressions}" if expressions else expressions 2671 sql = self.query_modifiers( 2672 expression, 2673 f"SELECT{top_distinct}{operation_modifiers}{kind}{expressions}", 2674 self.sql(expression, "into", comment=False), 2675 self.sql(expression, "from", comment=False), 2676 ) 2677 2678 # If both the CTE and SELECT clauses have comments, generate the latter earlier 2679 if expression.args.get("with"): 2680 sql = self.maybe_comment(sql, expression) 2681 expression.pop_comments() 2682 2683 sql = self.prepend_ctes(expression, sql) 2684 2685 if not self.SUPPORTS_SELECT_INTO and into: 2686 if into.args.get("temporary"): 2687 table_kind = " TEMPORARY" 2688 elif self.SUPPORTS_UNLOGGED_TABLES and into.args.get("unlogged"): 2689 table_kind = " UNLOGGED" 2690 else: 2691 table_kind = "" 2692 sql = f"CREATE{table_kind} TABLE {self.sql(into.this)} AS {sql}" 2693 2694 return sql
2706 def star_sql(self, expression: exp.Star) -> str: 2707 except_ = self.expressions(expression, key="except", flat=True) 2708 except_ = f"{self.seg(self.STAR_EXCEPT)} ({except_})" if except_ else "" 2709 replace = self.expressions(expression, key="replace", flat=True) 2710 replace = f"{self.seg('REPLACE')} ({replace})" if replace else "" 2711 rename = self.expressions(expression, key="rename", flat=True) 2712 rename = f"{self.seg('RENAME')} ({rename})" if rename else "" 2713 return f"*{except_}{replace}{rename}"
2729 def subquery_sql(self, expression: exp.Subquery, sep: str = " AS ") -> str: 2730 alias = self.sql(expression, "alias") 2731 alias = f"{sep}{alias}" if alias else "" 2732 sample = self.sql(expression, "sample") 2733 if self.dialect.ALIAS_POST_TABLESAMPLE and sample: 2734 alias = f"{sample}{alias}" 2735 2736 # Set to None so it's not generated again by self.query_modifiers() 2737 expression.set("sample", None) 2738 2739 pivots = self.expressions(expression, key="pivots", sep="", flat=True) 2740 sql = self.query_modifiers(expression, self.wrap(expression), alias, pivots) 2741 return self.prepend_ctes(expression, sql)
2747 def unnest_sql(self, expression: exp.Unnest) -> str: 2748 args = self.expressions(expression, flat=True) 2749 2750 alias = expression.args.get("alias") 2751 offset = expression.args.get("offset") 2752 2753 if self.UNNEST_WITH_ORDINALITY: 2754 if alias and isinstance(offset, exp.Expression): 2755 alias.append("columns", offset) 2756 2757 if alias and self.dialect.UNNEST_COLUMN_ONLY: 2758 columns = alias.columns 2759 alias = self.sql(columns[0]) if columns else "" 2760 else: 2761 alias = self.sql(alias) 2762 2763 alias = f" AS {alias}" if alias else alias 2764 if self.UNNEST_WITH_ORDINALITY: 2765 suffix = f" WITH ORDINALITY{alias}" if offset else alias 2766 else: 2767 if isinstance(offset, exp.Expression): 2768 suffix = f"{alias} WITH OFFSET AS {self.sql(offset)}" 2769 elif offset: 2770 suffix = f"{alias} WITH OFFSET" 2771 else: 2772 suffix = alias 2773 2774 return f"UNNEST({args}){suffix}"
2783 def window_sql(self, expression: exp.Window) -> str: 2784 this = self.sql(expression, "this") 2785 partition = self.partition_by_sql(expression) 2786 order = expression.args.get("order") 2787 order = self.order_sql(order, flat=True) if order else "" 2788 spec = self.sql(expression, "spec") 2789 alias = self.sql(expression, "alias") 2790 over = self.sql(expression, "over") or "OVER" 2791 2792 this = f"{this} {'AS' if expression.arg_key == 'windows' else over}" 2793 2794 first = expression.args.get("first") 2795 if first is None: 2796 first = "" 2797 else: 2798 first = "FIRST" if first else "LAST" 2799 2800 if not partition and not order and not spec and alias: 2801 return f"{this} {alias}" 2802 2803 args = self.format_args( 2804 *[arg for arg in (alias, first, partition, order, spec) if arg], sep=" " 2805 ) 2806 return f"{this} ({args})"
def
partition_by_sql( self, expression: sqlglot.expressions.Window | sqlglot.expressions.MatchRecognize) -> str:
2812 def windowspec_sql(self, expression: exp.WindowSpec) -> str: 2813 kind = self.sql(expression, "kind") 2814 start = csv(self.sql(expression, "start"), self.sql(expression, "start_side"), sep=" ") 2815 end = ( 2816 csv(self.sql(expression, "end"), self.sql(expression, "end_side"), sep=" ") 2817 or "CURRENT ROW" 2818 ) 2819 2820 window_spec = f"{kind} BETWEEN {start} AND {end}" 2821 2822 exclude = self.sql(expression, "exclude") 2823 if exclude: 2824 if self.SUPPORTS_WINDOW_EXCLUDE: 2825 window_spec += f" EXCLUDE {exclude}" 2826 else: 2827 self.unsupported("EXCLUDE clause is not supported in the WINDOW clause") 2828 2829 return window_spec
def
bracket_offset_expressions( self, expression: sqlglot.expressions.Bracket, index_offset: Optional[int] = None) -> List[sqlglot.expressions.Expression]:
2842 def bracket_offset_expressions( 2843 self, expression: exp.Bracket, index_offset: t.Optional[int] = None 2844 ) -> t.List[exp.Expression]: 2845 return apply_index_offset( 2846 expression.this, 2847 expression.expressions, 2848 (index_offset or self.dialect.INDEX_OFFSET) - expression.args.get("offset", 0), 2849 dialect=self.dialect, 2850 )
2860 def any_sql(self, expression: exp.Any) -> str: 2861 this = self.sql(expression, "this") 2862 if isinstance(expression.this, (*exp.UNWRAPPED_QUERIES, exp.Paren)): 2863 if isinstance(expression.this, exp.UNWRAPPED_QUERIES): 2864 this = self.wrap(this) 2865 return f"ANY{this}" 2866 return f"ANY {this}"
2871 def case_sql(self, expression: exp.Case) -> str: 2872 this = self.sql(expression, "this") 2873 statements = [f"CASE {this}" if this else "CASE"] 2874 2875 for e in expression.args["ifs"]: 2876 statements.append(f"WHEN {self.sql(e, 'this')}") 2877 statements.append(f"THEN {self.sql(e, 'true')}") 2878 2879 default = self.sql(expression, "default") 2880 2881 if default: 2882 statements.append(f"ELSE {default}") 2883 2884 statements.append("END") 2885 2886 if self.pretty and self.too_wide(statements): 2887 return self.indent("\n".join(statements), skip_first=True, skip_last=True) 2888 2889 return " ".join(statements)
2906 def trim_sql(self, expression: exp.Trim) -> str: 2907 trim_type = self.sql(expression, "position") 2908 2909 if trim_type == "LEADING": 2910 func_name = "LTRIM" 2911 elif trim_type == "TRAILING": 2912 func_name = "RTRIM" 2913 else: 2914 func_name = "TRIM" 2915 2916 return self.func(func_name, expression.this, expression.expression)
def
convert_concat_args( self, expression: sqlglot.expressions.Concat | sqlglot.expressions.ConcatWs) -> List[sqlglot.expressions.Expression]:
2918 def convert_concat_args(self, expression: exp.Concat | exp.ConcatWs) -> t.List[exp.Expression]: 2919 args = expression.expressions 2920 if isinstance(expression, exp.ConcatWs): 2921 args = args[1:] # Skip the delimiter 2922 2923 if self.dialect.STRICT_STRING_CONCAT and expression.args.get("safe"): 2924 args = [exp.cast(e, exp.DataType.Type.TEXT) for e in args] 2925 2926 if not self.dialect.CONCAT_COALESCE and expression.args.get("coalesce"): 2927 args = [exp.func("coalesce", e, exp.Literal.string("")) for e in args] 2928 2929 return args
2931 def concat_sql(self, expression: exp.Concat) -> str: 2932 expressions = self.convert_concat_args(expression) 2933 2934 # Some dialects don't allow a single-argument CONCAT call 2935 if not self.SUPPORTS_SINGLE_ARG_CONCAT and len(expressions) == 1: 2936 return self.sql(expressions[0]) 2937 2938 return self.func("CONCAT", *expressions)
2949 def foreignkey_sql(self, expression: exp.ForeignKey) -> str: 2950 expressions = self.expressions(expression, flat=True) 2951 expressions = f" ({expressions})" if expressions else "" 2952 reference = self.sql(expression, "reference") 2953 reference = f" {reference}" if reference else "" 2954 delete = self.sql(expression, "delete") 2955 delete = f" ON DELETE {delete}" if delete else "" 2956 update = self.sql(expression, "update") 2957 update = f" ON UPDATE {update}" if update else "" 2958 options = self.expressions(expression, key="options", flat=True, sep=" ") 2959 options = f" {options}" if options else "" 2960 return f"FOREIGN KEY{expressions}{reference}{delete}{update}{options}"
2962 def primarykey_sql(self, expression: exp.ForeignKey) -> str: 2963 expressions = self.expressions(expression, flat=True) 2964 options = self.expressions(expression, key="options", flat=True, sep=" ") 2965 options = f" {options}" if options else "" 2966 return f"PRIMARY KEY ({expressions}){options}"
2979 def jsonpath_sql(self, expression: exp.JSONPath) -> str: 2980 path = self.expressions(expression, sep="", flat=True).lstrip(".") 2981 2982 if expression.args.get("escape"): 2983 path = self.escape_str(path) 2984 2985 if self.QUOTE_JSON_PATH: 2986 path = f"{self.dialect.QUOTE_START}{path}{self.dialect.QUOTE_END}" 2987 2988 return path
2990 def json_path_part(self, expression: int | str | exp.JSONPathPart) -> str: 2991 if isinstance(expression, exp.JSONPathPart): 2992 transform = self.TRANSFORMS.get(expression.__class__) 2993 if not callable(transform): 2994 self.unsupported(f"Unsupported JSONPathPart type {expression.__class__.__name__}") 2995 return "" 2996 2997 return transform(self, expression) 2998 2999 if isinstance(expression, int): 3000 return str(expression) 3001 3002 if self._quote_json_path_key_using_brackets and self.JSON_PATH_SINGLE_QUOTE_ESCAPE: 3003 escaped = expression.replace("'", "\\'") 3004 escaped = f"\\'{expression}\\'" 3005 else: 3006 escaped = expression.replace('"', '\\"') 3007 escaped = f'"{escaped}"' 3008 3009 return escaped
def
jsonobject_sql( self, expression: sqlglot.expressions.JSONObject | sqlglot.expressions.JSONObjectAgg) -> str:
3014 def jsonobject_sql(self, expression: exp.JSONObject | exp.JSONObjectAgg) -> str: 3015 null_handling = expression.args.get("null_handling") 3016 null_handling = f" {null_handling}" if null_handling else "" 3017 3018 unique_keys = expression.args.get("unique_keys") 3019 if unique_keys is not None: 3020 unique_keys = f" {'WITH' if unique_keys else 'WITHOUT'} UNIQUE KEYS" 3021 else: 3022 unique_keys = "" 3023 3024 return_type = self.sql(expression, "return_type") 3025 return_type = f" RETURNING {return_type}" if return_type else "" 3026 encoding = self.sql(expression, "encoding") 3027 encoding = f" ENCODING {encoding}" if encoding else "" 3028 3029 return self.func( 3030 "JSON_OBJECT" if isinstance(expression, exp.JSONObject) else "JSON_OBJECTAGG", 3031 *expression.expressions, 3032 suffix=f"{null_handling}{unique_keys}{return_type}{encoding})", 3033 )
3038 def jsonarray_sql(self, expression: exp.JSONArray) -> str: 3039 null_handling = expression.args.get("null_handling") 3040 null_handling = f" {null_handling}" if null_handling else "" 3041 return_type = self.sql(expression, "return_type") 3042 return_type = f" RETURNING {return_type}" if return_type else "" 3043 strict = " STRICT" if expression.args.get("strict") else "" 3044 return self.func( 3045 "JSON_ARRAY", *expression.expressions, suffix=f"{null_handling}{return_type}{strict})" 3046 )
3048 def jsonarrayagg_sql(self, expression: exp.JSONArrayAgg) -> str: 3049 this = self.sql(expression, "this") 3050 order = self.sql(expression, "order") 3051 null_handling = expression.args.get("null_handling") 3052 null_handling = f" {null_handling}" if null_handling else "" 3053 return_type = self.sql(expression, "return_type") 3054 return_type = f" RETURNING {return_type}" if return_type else "" 3055 strict = " STRICT" if expression.args.get("strict") else "" 3056 return self.func( 3057 "JSON_ARRAYAGG", 3058 this, 3059 suffix=f"{order}{null_handling}{return_type}{strict})", 3060 )
3062 def jsoncolumndef_sql(self, expression: exp.JSONColumnDef) -> str: 3063 path = self.sql(expression, "path") 3064 path = f" PATH {path}" if path else "" 3065 nested_schema = self.sql(expression, "nested_schema") 3066 3067 if nested_schema: 3068 return f"NESTED{path} {nested_schema}" 3069 3070 this = self.sql(expression, "this") 3071 kind = self.sql(expression, "kind") 3072 kind = f" {kind}" if kind else "" 3073 return f"{this}{kind}{path}"
3078 def jsontable_sql(self, expression: exp.JSONTable) -> str: 3079 this = self.sql(expression, "this") 3080 path = self.sql(expression, "path") 3081 path = f", {path}" if path else "" 3082 error_handling = expression.args.get("error_handling") 3083 error_handling = f" {error_handling}" if error_handling else "" 3084 empty_handling = expression.args.get("empty_handling") 3085 empty_handling = f" {empty_handling}" if empty_handling else "" 3086 schema = self.sql(expression, "schema") 3087 return self.func( 3088 "JSON_TABLE", this, suffix=f"{path}{error_handling}{empty_handling} {schema})" 3089 )
3091 def openjsoncolumndef_sql(self, expression: exp.OpenJSONColumnDef) -> str: 3092 this = self.sql(expression, "this") 3093 kind = self.sql(expression, "kind") 3094 path = self.sql(expression, "path") 3095 path = f" {path}" if path else "" 3096 as_json = " AS JSON" if expression.args.get("as_json") else "" 3097 return f"{this} {kind}{path}{as_json}"
3099 def openjson_sql(self, expression: exp.OpenJSON) -> str: 3100 this = self.sql(expression, "this") 3101 path = self.sql(expression, "path") 3102 path = f", {path}" if path else "" 3103 expressions = self.expressions(expression) 3104 with_ = ( 3105 f" WITH ({self.seg(self.indent(expressions), sep='')}{self.seg(')', sep='')}" 3106 if expressions 3107 else "" 3108 ) 3109 return f"OPENJSON({this}{path}){with_}"
3111 def in_sql(self, expression: exp.In) -> str: 3112 query = expression.args.get("query") 3113 unnest = expression.args.get("unnest") 3114 field = expression.args.get("field") 3115 is_global = " GLOBAL" if expression.args.get("is_global") else "" 3116 3117 if query: 3118 in_sql = self.sql(query) 3119 elif unnest: 3120 in_sql = self.in_unnest_op(unnest) 3121 elif field: 3122 in_sql = self.sql(field) 3123 else: 3124 in_sql = f"({self.expressions(expression, dynamic=True, new_line=True, skip_first=True, skip_last=True)})" 3125 3126 return f"{self.sql(expression, 'this')}{is_global} IN {in_sql}"
3131 def interval_sql(self, expression: exp.Interval) -> str: 3132 unit = self.sql(expression, "unit") 3133 if not self.INTERVAL_ALLOWS_PLURAL_FORM: 3134 unit = self.TIME_PART_SINGULARS.get(unit, unit) 3135 unit = f" {unit}" if unit else "" 3136 3137 if self.SINGLE_STRING_INTERVAL: 3138 this = expression.this.name if expression.this else "" 3139 return f"INTERVAL '{this}{unit}'" if this else f"INTERVAL{unit}" 3140 3141 this = self.sql(expression, "this") 3142 if this: 3143 unwrapped = isinstance(expression.this, self.UNWRAPPED_INTERVAL_VALUES) 3144 this = f" {this}" if unwrapped else f" ({this})" 3145 3146 return f"INTERVAL{this}{unit}"
3151 def reference_sql(self, expression: exp.Reference) -> str: 3152 this = self.sql(expression, "this") 3153 expressions = self.expressions(expression, flat=True) 3154 expressions = f"({expressions})" if expressions else "" 3155 options = self.expressions(expression, key="options", flat=True, sep=" ") 3156 options = f" {options}" if options else "" 3157 return f"REFERENCES {this}{expressions}{options}"
3159 def anonymous_sql(self, expression: exp.Anonymous) -> str: 3160 # We don't normalize qualified functions such as a.b.foo(), because they can be case-sensitive 3161 parent = expression.parent 3162 is_qualified = isinstance(parent, exp.Dot) and expression is parent.expression 3163 return self.func( 3164 self.sql(expression, "this"), *expression.expressions, normalize=not is_qualified 3165 )
3185 def pivotalias_sql(self, expression: exp.PivotAlias) -> str: 3186 alias = expression.args["alias"] 3187 3188 parent = expression.parent 3189 pivot = parent and parent.parent 3190 3191 if isinstance(pivot, exp.Pivot) and pivot.unpivot: 3192 identifier_alias = isinstance(alias, exp.Identifier) 3193 literal_alias = isinstance(alias, exp.Literal) 3194 3195 if identifier_alias and not self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3196 alias.replace(exp.Literal.string(alias.output_name)) 3197 elif not identifier_alias and literal_alias and self.UNPIVOT_ALIASES_ARE_IDENTIFIERS: 3198 alias.replace(exp.to_identifier(alias.output_name)) 3199 3200 return self.alias_sql(expression)
def
and_sql( self, expression: sqlglot.expressions.And, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
or_sql( self, expression: sqlglot.expressions.Or, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
xor_sql( self, expression: sqlglot.expressions.Xor, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
def
connector_sql( self, expression: sqlglot.expressions.Connector, op: str, stack: Optional[List[str | sqlglot.expressions.Expression]] = None) -> str:
3238 def connector_sql( 3239 self, 3240 expression: exp.Connector, 3241 op: str, 3242 stack: t.Optional[t.List[str | exp.Expression]] = None, 3243 ) -> str: 3244 if stack is not None: 3245 if expression.expressions: 3246 stack.append(self.expressions(expression, sep=f" {op} ")) 3247 else: 3248 stack.append(expression.right) 3249 if expression.comments and self.comments: 3250 for comment in expression.comments: 3251 if comment: 3252 op += f" /*{self.pad_comment(comment)}*/" 3253 stack.extend((op, expression.left)) 3254 return op 3255 3256 stack = [expression] 3257 sqls: t.List[str] = [] 3258 ops = set() 3259 3260 while stack: 3261 node = stack.pop() 3262 if isinstance(node, exp.Connector): 3263 ops.add(getattr(self, f"{node.key}_sql")(node, stack)) 3264 else: 3265 sql = self.sql(node) 3266 if sqls and sqls[-1] in ops: 3267 sqls[-1] += f" {sql}" 3268 else: 3269 sqls.append(sql) 3270 3271 sep = "\n" if self.pretty and self.too_wide(sqls) else " " 3272 return sep.join(sqls)
def
cast_sql( self, expression: sqlglot.expressions.Cast, safe_prefix: Optional[str] = None) -> str:
3292 def cast_sql(self, expression: exp.Cast, safe_prefix: t.Optional[str] = None) -> str: 3293 format_sql = self.sql(expression, "format") 3294 format_sql = f" FORMAT {format_sql}" if format_sql else "" 3295 to_sql = self.sql(expression, "to") 3296 to_sql = f" {to_sql}" if to_sql else "" 3297 action = self.sql(expression, "action") 3298 action = f" {action}" if action else "" 3299 default = self.sql(expression, "default") 3300 default = f" DEFAULT {default} ON CONVERSION ERROR" if default else "" 3301 return f"{safe_prefix or ''}CAST({self.sql(expression, 'this')} AS{to_sql}{default}{format_sql}{action})"
3315 def comment_sql(self, expression: exp.Comment) -> str: 3316 this = self.sql(expression, "this") 3317 kind = expression.args["kind"] 3318 materialized = " MATERIALIZED" if expression.args.get("materialized") else "" 3319 exists_sql = " IF EXISTS " if expression.args.get("exists") else " " 3320 expression_sql = self.sql(expression, "expression") 3321 return f"COMMENT{exists_sql}ON{materialized} {kind} {this} IS {expression_sql}"
3323 def mergetreettlaction_sql(self, expression: exp.MergeTreeTTLAction) -> str: 3324 this = self.sql(expression, "this") 3325 delete = " DELETE" if expression.args.get("delete") else "" 3326 recompress = self.sql(expression, "recompress") 3327 recompress = f" RECOMPRESS {recompress}" if recompress else "" 3328 to_disk = self.sql(expression, "to_disk") 3329 to_disk = f" TO DISK {to_disk}" if to_disk else "" 3330 to_volume = self.sql(expression, "to_volume") 3331 to_volume = f" TO VOLUME {to_volume}" if to_volume else "" 3332 return f"{this}{delete}{recompress}{to_disk}{to_volume}"
3334 def mergetreettl_sql(self, expression: exp.MergeTreeTTL) -> str: 3335 where = self.sql(expression, "where") 3336 group = self.sql(expression, "group") 3337 aggregates = self.expressions(expression, key="aggregates") 3338 aggregates = self.seg("SET") + self.seg(aggregates) if aggregates else "" 3339 3340 if not (where or group or aggregates) and len(expression.expressions) == 1: 3341 return f"TTL {self.expressions(expression, flat=True)}" 3342 3343 return f"TTL{self.seg(self.expressions(expression))}{where}{group}{aggregates}"
3360 def altercolumn_sql(self, expression: exp.AlterColumn) -> str: 3361 this = self.sql(expression, "this") 3362 3363 dtype = self.sql(expression, "dtype") 3364 if dtype: 3365 collate = self.sql(expression, "collate") 3366 collate = f" COLLATE {collate}" if collate else "" 3367 using = self.sql(expression, "using") 3368 using = f" USING {using}" if using else "" 3369 alter_set_type = self.ALTER_SET_TYPE + " " if self.ALTER_SET_TYPE else "" 3370 return f"ALTER COLUMN {this} {alter_set_type}{dtype}{collate}{using}" 3371 3372 default = self.sql(expression, "default") 3373 if default: 3374 return f"ALTER COLUMN {this} SET DEFAULT {default}" 3375 3376 comment = self.sql(expression, "comment") 3377 if comment: 3378 return f"ALTER COLUMN {this} COMMENT {comment}" 3379 3380 visible = expression.args.get("visible") 3381 if visible: 3382 return f"ALTER COLUMN {this} SET {visible}" 3383 3384 allow_null = expression.args.get("allow_null") 3385 drop = expression.args.get("drop") 3386 3387 if not drop and not allow_null: 3388 self.unsupported("Unsupported ALTER COLUMN syntax") 3389 3390 if allow_null is not None: 3391 keyword = "DROP" if drop else "SET" 3392 return f"ALTER COLUMN {this} {keyword} NOT NULL" 3393 3394 return f"ALTER COLUMN {this} DROP DEFAULT"
3410 def altersortkey_sql(self, expression: exp.AlterSortKey) -> str: 3411 compound = " COMPOUND" if expression.args.get("compound") else "" 3412 this = self.sql(expression, "this") 3413 expressions = self.expressions(expression, flat=True) 3414 expressions = f"({expressions})" if expressions else "" 3415 return f"ALTER{compound} SORTKEY {this or expressions}"
3417 def alterrename_sql(self, expression: exp.AlterRename) -> str: 3418 if not self.RENAME_TABLE_WITH_DB: 3419 # Remove db from tables 3420 expression = expression.transform( 3421 lambda n: exp.table_(n.this) if isinstance(n, exp.Table) else n 3422 ).assert_is(exp.AlterRename) 3423 this = self.sql(expression, "this") 3424 return f"RENAME TO {this}"
3436 def alter_sql(self, expression: exp.Alter) -> str: 3437 actions = expression.args["actions"] 3438 3439 if isinstance(actions[0], exp.ColumnDef): 3440 actions = self.add_column_sql(expression) 3441 elif isinstance(actions[0], exp.Schema): 3442 actions = self.expressions(expression, key="actions", prefix="ADD COLUMNS ") 3443 elif isinstance(actions[0], exp.Delete): 3444 actions = self.expressions(expression, key="actions", flat=True) 3445 elif isinstance(actions[0], exp.Query): 3446 actions = "AS " + self.expressions(expression, key="actions") 3447 else: 3448 actions = self.expressions(expression, key="actions", flat=True) 3449 3450 exists = " IF EXISTS" if expression.args.get("exists") else "" 3451 on_cluster = self.sql(expression, "cluster") 3452 on_cluster = f" {on_cluster}" if on_cluster else "" 3453 only = " ONLY" if expression.args.get("only") else "" 3454 options = self.expressions(expression, key="options") 3455 options = f", {options}" if options else "" 3456 kind = self.sql(expression, "kind") 3457 not_valid = " NOT VALID" if expression.args.get("not_valid") else "" 3458 3459 return f"ALTER {kind}{exists}{only} {self.sql(expression, 'this')}{on_cluster} {actions}{not_valid}{options}"
3461 def add_column_sql(self, expression: exp.Alter) -> str: 3462 if self.ALTER_TABLE_INCLUDE_COLUMN_KEYWORD: 3463 return self.expressions( 3464 expression, 3465 key="actions", 3466 prefix="ADD COLUMN ", 3467 skip_first=True, 3468 ) 3469 return f"ADD {self.expressions(expression, key='actions', flat=True)}"
3479 def distinct_sql(self, expression: exp.Distinct) -> str: 3480 this = self.expressions(expression, flat=True) 3481 3482 if not self.MULTI_ARG_DISTINCT and len(expression.expressions) > 1: 3483 case = exp.case() 3484 for arg in expression.expressions: 3485 case = case.when(arg.is_(exp.null()), exp.null()) 3486 this = self.sql(case.else_(f"({this})")) 3487 3488 this = f" {this}" if this else "" 3489 3490 on = self.sql(expression, "on") 3491 on = f" ON {on}" if on else "" 3492 return f"DISTINCT{this}{on}"
3521 def div_sql(self, expression: exp.Div) -> str: 3522 l, r = expression.left, expression.right 3523 3524 if not self.dialect.SAFE_DIVISION and expression.args.get("safe"): 3525 r.replace(exp.Nullif(this=r.copy(), expression=exp.Literal.number(0))) 3526 3527 if self.dialect.TYPED_DIVISION and not expression.args.get("typed"): 3528 if not l.is_type(*exp.DataType.REAL_TYPES) and not r.is_type(*exp.DataType.REAL_TYPES): 3529 l.replace(exp.cast(l.copy(), to=exp.DataType.Type.DOUBLE)) 3530 3531 elif not self.dialect.TYPED_DIVISION and expression.args.get("typed"): 3532 if l.is_type(*exp.DataType.INTEGER_TYPES) and r.is_type(*exp.DataType.INTEGER_TYPES): 3533 return self.sql( 3534 exp.cast( 3535 l / r, 3536 to=exp.DataType.Type.BIGINT, 3537 ) 3538 ) 3539 3540 return self.binary(expression, "/")
3636 def log_sql(self, expression: exp.Log) -> str: 3637 this = expression.this 3638 expr = expression.expression 3639 3640 if self.dialect.LOG_BASE_FIRST is False: 3641 this, expr = expr, this 3642 elif self.dialect.LOG_BASE_FIRST is None and expr: 3643 if this.name in ("2", "10"): 3644 return self.func(f"LOG{this.name}", expr) 3645 3646 self.unsupported(f"Unsupported logarithm with base {self.sql(this)}") 3647 3648 return self.func("LOG", this, expr)
3657 def binary(self, expression: exp.Binary, op: str) -> str: 3658 sqls: t.List[str] = [] 3659 stack: t.List[t.Union[str, exp.Expression]] = [expression] 3660 binary_type = type(expression) 3661 3662 while stack: 3663 node = stack.pop() 3664 3665 if type(node) is binary_type: 3666 op_func = node.args.get("operator") 3667 if op_func: 3668 op = f"OPERATOR({self.sql(op_func)})" 3669 3670 stack.append(node.right) 3671 stack.append(f" {self.maybe_comment(op, comments=node.comments)} ") 3672 stack.append(node.left) 3673 else: 3674 sqls.append(self.sql(node)) 3675 3676 return "".join(sqls)
3685 def function_fallback_sql(self, expression: exp.Func) -> str: 3686 args = [] 3687 3688 for key in expression.arg_types: 3689 arg_value = expression.args.get(key) 3690 3691 if isinstance(arg_value, list): 3692 for value in arg_value: 3693 args.append(value) 3694 elif arg_value is not None: 3695 args.append(arg_value) 3696 3697 if self.dialect.PRESERVE_ORIGINAL_NAMES: 3698 name = (expression._meta and expression.meta.get("name")) or expression.sql_name() 3699 else: 3700 name = expression.sql_name() 3701 3702 return self.func(name, *args)
def
func( self, name: str, *args: Union[str, sqlglot.expressions.Expression, NoneType], prefix: str = '(', suffix: str = ')', normalize: bool = True) -> str:
3704 def func( 3705 self, 3706 name: str, 3707 *args: t.Optional[exp.Expression | str], 3708 prefix: str = "(", 3709 suffix: str = ")", 3710 normalize: bool = True, 3711 ) -> str: 3712 name = self.normalize_func(name) if normalize else name 3713 return f"{name}{prefix}{self.format_args(*args)}{suffix}"
def
format_args( self, *args: Union[str, sqlglot.expressions.Expression, NoneType], sep: str = ', ') -> str:
3715 def format_args(self, *args: t.Optional[str | exp.Expression], sep: str = ", ") -> str: 3716 arg_sqls = tuple( 3717 self.sql(arg) for arg in args if arg is not None and not isinstance(arg, bool) 3718 ) 3719 if self.pretty and self.too_wide(arg_sqls): 3720 return self.indent( 3721 "\n" + f"{sep.strip()}\n".join(arg_sqls) + "\n", skip_first=True, skip_last=True 3722 ) 3723 return sep.join(arg_sqls)
def
format_time( self, expression: sqlglot.expressions.Expression, inverse_time_mapping: Optional[Dict[str, str]] = None, inverse_time_trie: Optional[Dict] = None) -> Optional[str]:
3728 def format_time( 3729 self, 3730 expression: exp.Expression, 3731 inverse_time_mapping: t.Optional[t.Dict[str, str]] = None, 3732 inverse_time_trie: t.Optional[t.Dict] = None, 3733 ) -> t.Optional[str]: 3734 return format_time( 3735 self.sql(expression, "format"), 3736 inverse_time_mapping or self.dialect.INVERSE_TIME_MAPPING, 3737 inverse_time_trie or self.dialect.INVERSE_TIME_TRIE, 3738 )
def
expressions( self, expression: Optional[sqlglot.expressions.Expression] = None, key: Optional[str] = None, sqls: Optional[Collection[Union[str, sqlglot.expressions.Expression]]] = None, flat: bool = False, indent: bool = True, skip_first: bool = False, skip_last: bool = False, sep: str = ', ', prefix: str = '', dynamic: bool = False, new_line: bool = False) -> str:
3740 def expressions( 3741 self, 3742 expression: t.Optional[exp.Expression] = None, 3743 key: t.Optional[str] = None, 3744 sqls: t.Optional[t.Collection[str | exp.Expression]] = None, 3745 flat: bool = False, 3746 indent: bool = True, 3747 skip_first: bool = False, 3748 skip_last: bool = False, 3749 sep: str = ", ", 3750 prefix: str = "", 3751 dynamic: bool = False, 3752 new_line: bool = False, 3753 ) -> str: 3754 expressions = expression.args.get(key or "expressions") if expression else sqls 3755 3756 if not expressions: 3757 return "" 3758 3759 if flat: 3760 return sep.join(sql for sql in (self.sql(e) for e in expressions) if sql) 3761 3762 num_sqls = len(expressions) 3763 result_sqls = [] 3764 3765 for i, e in enumerate(expressions): 3766 sql = self.sql(e, comment=False) 3767 if not sql: 3768 continue 3769 3770 comments = self.maybe_comment("", e) if isinstance(e, exp.Expression) else "" 3771 3772 if self.pretty: 3773 if self.leading_comma: 3774 result_sqls.append(f"{sep if i > 0 else ''}{prefix}{sql}{comments}") 3775 else: 3776 result_sqls.append( 3777 f"{prefix}{sql}{(sep.rstrip() if comments else sep) if i + 1 < num_sqls else ''}{comments}" 3778 ) 3779 else: 3780 result_sqls.append(f"{prefix}{sql}{comments}{sep if i + 1 < num_sqls else ''}") 3781 3782 if self.pretty and (not dynamic or self.too_wide(result_sqls)): 3783 if new_line: 3784 result_sqls.insert(0, "") 3785 result_sqls.append("") 3786 result_sql = "\n".join(s.rstrip() for s in result_sqls) 3787 else: 3788 result_sql = "".join(result_sqls) 3789 3790 return ( 3791 self.indent(result_sql, skip_first=skip_first, skip_last=skip_last) 3792 if indent 3793 else result_sql 3794 )
def
op_expressions( self, op: str, expression: sqlglot.expressions.Expression, flat: bool = False) -> str:
3796 def op_expressions(self, op: str, expression: exp.Expression, flat: bool = False) -> str: 3797 flat = flat or isinstance(expression.parent, exp.Properties) 3798 expressions_sql = self.expressions(expression, flat=flat) 3799 if flat: 3800 return f"{op} {expressions_sql}" 3801 return f"{self.seg(op)}{self.sep() if expressions_sql else ''}{expressions_sql}"
3803 def naked_property(self, expression: exp.Property) -> str: 3804 property_name = exp.Properties.PROPERTY_TO_NAME.get(expression.__class__) 3805 if not property_name: 3806 self.unsupported(f"Unsupported property {expression.__class__.__name__}") 3807 return f"{property_name} {self.sql(expression, 'this')}"
3815 def userdefinedfunction_sql(self, expression: exp.UserDefinedFunction) -> str: 3816 this = self.sql(expression, "this") 3817 expressions = self.no_identify(self.expressions, expression) 3818 expressions = ( 3819 self.wrap(expressions) if expression.args.get("wrapped") else f" {expressions}" 3820 ) 3821 return f"{this}{expressions}" if expressions.strip() != "" else this
3831 def when_sql(self, expression: exp.When) -> str: 3832 matched = "MATCHED" if expression.args["matched"] else "NOT MATCHED" 3833 source = " BY SOURCE" if self.MATCHED_BY_SOURCE and expression.args.get("source") else "" 3834 condition = self.sql(expression, "condition") 3835 condition = f" AND {condition}" if condition else "" 3836 3837 then_expression = expression.args.get("then") 3838 if isinstance(then_expression, exp.Insert): 3839 this = self.sql(then_expression, "this") 3840 this = f"INSERT {this}" if this else "INSERT" 3841 then = self.sql(then_expression, "expression") 3842 then = f"{this} VALUES {then}" if then else this 3843 elif isinstance(then_expression, exp.Update): 3844 if isinstance(then_expression.args.get("expressions"), exp.Star): 3845 then = f"UPDATE {self.sql(then_expression, 'expressions')}" 3846 else: 3847 then = f"UPDATE SET{self.sep()}{self.expressions(then_expression)}" 3848 else: 3849 then = self.sql(then_expression) 3850 return f"WHEN {matched}{source}{condition} THEN {then}"
3855 def merge_sql(self, expression: exp.Merge) -> str: 3856 table = expression.this 3857 table_alias = "" 3858 3859 hints = table.args.get("hints") 3860 if hints and table.alias and isinstance(hints[0], exp.WithTableHint): 3861 # T-SQL syntax is MERGE ... <target_table> [WITH (<merge_hint>)] [[AS] table_alias] 3862 table_alias = f" AS {self.sql(table.args['alias'].pop())}" 3863 3864 this = self.sql(table) 3865 using = f"USING {self.sql(expression, 'using')}" 3866 on = f"ON {self.sql(expression, 'on')}" 3867 whens = self.sql(expression, "whens") 3868 3869 returning = self.sql(expression, "returning") 3870 if returning: 3871 whens = f"{whens}{returning}" 3872 3873 sep = self.sep() 3874 3875 return self.prepend_ctes( 3876 expression, 3877 f"MERGE INTO {this}{table_alias}{sep}{using}{sep}{on}{sep}{whens}", 3878 )
3884 def tonumber_sql(self, expression: exp.ToNumber) -> str: 3885 if not self.SUPPORTS_TO_NUMBER: 3886 self.unsupported("Unsupported TO_NUMBER function") 3887 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3888 3889 fmt = expression.args.get("format") 3890 if not fmt: 3891 self.unsupported("Conversion format is required for TO_NUMBER") 3892 return self.sql(exp.cast(expression.this, exp.DataType.Type.DOUBLE)) 3893 3894 return self.func("TO_NUMBER", expression.this, fmt)
3896 def dictproperty_sql(self, expression: exp.DictProperty) -> str: 3897 this = self.sql(expression, "this") 3898 kind = self.sql(expression, "kind") 3899 settings_sql = self.expressions(expression, key="settings", sep=" ") 3900 args = f"({self.sep('')}{settings_sql}{self.seg(')', sep='')}" if settings_sql else "()" 3901 return f"{this}({kind}{args})"
3920 def distributedbyproperty_sql(self, expression: exp.DistributedByProperty) -> str: 3921 expressions = self.expressions(expression, flat=True) 3922 expressions = f" {self.wrap(expressions)}" if expressions else "" 3923 buckets = self.sql(expression, "buckets") 3924 kind = self.sql(expression, "kind") 3925 buckets = f" BUCKETS {buckets}" if buckets else "" 3926 order = self.sql(expression, "order") 3927 return f"DISTRIBUTED BY {kind}{expressions}{buckets}{order}"
3932 def clusteredbyproperty_sql(self, expression: exp.ClusteredByProperty) -> str: 3933 expressions = self.expressions(expression, key="expressions", flat=True) 3934 sorted_by = self.expressions(expression, key="sorted_by", flat=True) 3935 sorted_by = f" SORTED BY ({sorted_by})" if sorted_by else "" 3936 buckets = self.sql(expression, "buckets") 3937 return f"CLUSTERED BY ({expressions}){sorted_by} INTO {buckets} BUCKETS"
3939 def anyvalue_sql(self, expression: exp.AnyValue) -> str: 3940 this = self.sql(expression, "this") 3941 having = self.sql(expression, "having") 3942 3943 if having: 3944 this = f"{this} HAVING {'MAX' if expression.args.get('max') else 'MIN'} {having}" 3945 3946 return self.func("ANY_VALUE", this)
3948 def querytransform_sql(self, expression: exp.QueryTransform) -> str: 3949 transform = self.func("TRANSFORM", *expression.expressions) 3950 row_format_before = self.sql(expression, "row_format_before") 3951 row_format_before = f" {row_format_before}" if row_format_before else "" 3952 record_writer = self.sql(expression, "record_writer") 3953 record_writer = f" RECORDWRITER {record_writer}" if record_writer else "" 3954 using = f" USING {self.sql(expression, 'command_script')}" 3955 schema = self.sql(expression, "schema") 3956 schema = f" AS {schema}" if schema else "" 3957 row_format_after = self.sql(expression, "row_format_after") 3958 row_format_after = f" {row_format_after}" if row_format_after else "" 3959 record_reader = self.sql(expression, "record_reader") 3960 record_reader = f" RECORDREADER {record_reader}" if record_reader else "" 3961 return f"{transform}{row_format_before}{record_writer}{using}{schema}{row_format_after}{record_reader}"
3963 def indexconstraintoption_sql(self, expression: exp.IndexConstraintOption) -> str: 3964 key_block_size = self.sql(expression, "key_block_size") 3965 if key_block_size: 3966 return f"KEY_BLOCK_SIZE = {key_block_size}" 3967 3968 using = self.sql(expression, "using") 3969 if using: 3970 return f"USING {using}" 3971 3972 parser = self.sql(expression, "parser") 3973 if parser: 3974 return f"WITH PARSER {parser}" 3975 3976 comment = self.sql(expression, "comment") 3977 if comment: 3978 return f"COMMENT {comment}" 3979 3980 visible = expression.args.get("visible") 3981 if visible is not None: 3982 return "VISIBLE" if visible else "INVISIBLE" 3983 3984 engine_attr = self.sql(expression, "engine_attr") 3985 if engine_attr: 3986 return f"ENGINE_ATTRIBUTE = {engine_attr}" 3987 3988 secondary_engine_attr = self.sql(expression, "secondary_engine_attr") 3989 if secondary_engine_attr: 3990 return f"SECONDARY_ENGINE_ATTRIBUTE = {secondary_engine_attr}" 3991 3992 self.unsupported("Unsupported index constraint option.") 3993 return ""
3999 def indexcolumnconstraint_sql(self, expression: exp.IndexColumnConstraint) -> str: 4000 kind = self.sql(expression, "kind") 4001 kind = f"{kind} INDEX" if kind else "INDEX" 4002 this = self.sql(expression, "this") 4003 this = f" {this}" if this else "" 4004 index_type = self.sql(expression, "index_type") 4005 index_type = f" USING {index_type}" if index_type else "" 4006 expressions = self.expressions(expression, flat=True) 4007 expressions = f" ({expressions})" if expressions else "" 4008 options = self.expressions(expression, key="options", sep=" ") 4009 options = f" {options}" if options else "" 4010 return f"{kind}{this}{index_type}{expressions}{options}"
4012 def nvl2_sql(self, expression: exp.Nvl2) -> str: 4013 if self.NVL2_SUPPORTED: 4014 return self.function_fallback_sql(expression) 4015 4016 case = exp.Case().when( 4017 expression.this.is_(exp.null()).not_(copy=False), 4018 expression.args["true"], 4019 copy=False, 4020 ) 4021 else_cond = expression.args.get("false") 4022 if else_cond: 4023 case.else_(else_cond, copy=False) 4024 4025 return self.sql(case)
4027 def comprehension_sql(self, expression: exp.Comprehension) -> str: 4028 this = self.sql(expression, "this") 4029 expr = self.sql(expression, "expression") 4030 iterator = self.sql(expression, "iterator") 4031 condition = self.sql(expression, "condition") 4032 condition = f" IF {condition}" if condition else "" 4033 return f"{this} FOR {expr} IN {iterator}{condition}"
4041 def predict_sql(self, expression: exp.Predict) -> str: 4042 model = self.sql(expression, "this") 4043 model = f"MODEL {model}" 4044 table = self.sql(expression, "expression") 4045 table = f"TABLE {table}" if not isinstance(expression.expression, exp.Subquery) else table 4046 parameters = self.sql(expression, "params_struct") 4047 return self.func("PREDICT", model, table, parameters or None)
4059 def toarray_sql(self, expression: exp.ToArray) -> str: 4060 arg = expression.this 4061 if not arg.type: 4062 from sqlglot.optimizer.annotate_types import annotate_types 4063 4064 arg = annotate_types(arg, dialect=self.dialect) 4065 4066 if arg.is_type(exp.DataType.Type.ARRAY): 4067 return self.sql(arg) 4068 4069 cond_for_null = arg.is_(exp.null()) 4070 return self.sql(exp.func("IF", cond_for_null, exp.null(), exp.array(arg, copy=False)))
4072 def tsordstotime_sql(self, expression: exp.TsOrDsToTime) -> str: 4073 this = expression.this 4074 time_format = self.format_time(expression) 4075 4076 if time_format: 4077 return self.sql( 4078 exp.cast( 4079 exp.StrToTime(this=this, format=expression.args["format"]), 4080 exp.DataType.Type.TIME, 4081 ) 4082 ) 4083 4084 if isinstance(this, exp.TsOrDsToTime) or this.is_type(exp.DataType.Type.TIME): 4085 return self.sql(this) 4086 4087 return self.sql(exp.cast(this, exp.DataType.Type.TIME))
4089 def tsordstotimestamp_sql(self, expression: exp.TsOrDsToTimestamp) -> str: 4090 this = expression.this 4091 if isinstance(this, exp.TsOrDsToTimestamp) or this.is_type(exp.DataType.Type.TIMESTAMP): 4092 return self.sql(this) 4093 4094 return self.sql(exp.cast(this, exp.DataType.Type.TIMESTAMP, dialect=self.dialect))
4096 def tsordstodatetime_sql(self, expression: exp.TsOrDsToDatetime) -> str: 4097 this = expression.this 4098 if isinstance(this, exp.TsOrDsToDatetime) or this.is_type(exp.DataType.Type.DATETIME): 4099 return self.sql(this) 4100 4101 return self.sql(exp.cast(this, exp.DataType.Type.DATETIME, dialect=self.dialect))
4103 def tsordstodate_sql(self, expression: exp.TsOrDsToDate) -> str: 4104 this = expression.this 4105 time_format = self.format_time(expression) 4106 4107 if time_format and time_format not in (self.dialect.TIME_FORMAT, self.dialect.DATE_FORMAT): 4108 return self.sql( 4109 exp.cast( 4110 exp.StrToTime(this=this, format=expression.args["format"]), 4111 exp.DataType.Type.DATE, 4112 ) 4113 ) 4114 4115 if isinstance(this, exp.TsOrDsToDate) or this.is_type(exp.DataType.Type.DATE): 4116 return self.sql(this) 4117 4118 return self.sql(exp.cast(this, exp.DataType.Type.DATE))
4130 def lastday_sql(self, expression: exp.LastDay) -> str: 4131 if self.LAST_DAY_SUPPORTS_DATE_PART: 4132 return self.function_fallback_sql(expression) 4133 4134 unit = expression.text("unit") 4135 if unit and unit != "MONTH": 4136 self.unsupported("Date parts are not supported in LAST_DAY.") 4137 4138 return self.func("LAST_DAY", expression.this)
4147 def arrayany_sql(self, expression: exp.ArrayAny) -> str: 4148 if self.CAN_IMPLEMENT_ARRAY_ANY: 4149 filtered = exp.ArrayFilter(this=expression.this, expression=expression.expression) 4150 filtered_not_empty = exp.ArraySize(this=filtered).neq(0) 4151 original_is_empty = exp.ArraySize(this=expression.this).eq(0) 4152 return self.sql(exp.paren(original_is_empty.or_(filtered_not_empty))) 4153 4154 from sqlglot.dialects import Dialect 4155 4156 # SQLGlot's executor supports ARRAY_ANY, so we don't wanna warn for the SQLGlot dialect 4157 if self.dialect.__class__ != Dialect: 4158 self.unsupported("ARRAY_ANY is unsupported") 4159 4160 return self.function_fallback_sql(expression)
4162 def struct_sql(self, expression: exp.Struct) -> str: 4163 expression.set( 4164 "expressions", 4165 [ 4166 exp.alias_(e.expression, e.name if e.this.is_string else e.this) 4167 if isinstance(e, exp.PropertyEQ) 4168 else e 4169 for e in expression.expressions 4170 ], 4171 ) 4172 4173 return self.function_fallback_sql(expression)
4181 def truncatetable_sql(self, expression: exp.TruncateTable) -> str: 4182 target = "DATABASE" if expression.args.get("is_database") else "TABLE" 4183 tables = f" {self.expressions(expression)}" 4184 4185 exists = " IF EXISTS" if expression.args.get("exists") else "" 4186 4187 on_cluster = self.sql(expression, "cluster") 4188 on_cluster = f" {on_cluster}" if on_cluster else "" 4189 4190 identity = self.sql(expression, "identity") 4191 identity = f" {identity} IDENTITY" if identity else "" 4192 4193 option = self.sql(expression, "option") 4194 option = f" {option}" if option else "" 4195 4196 partition = self.sql(expression, "partition") 4197 partition = f" {partition}" if partition else "" 4198 4199 return f"TRUNCATE {target}{exists}{tables}{on_cluster}{identity}{option}{partition}"
4203 def convert_sql(self, expression: exp.Convert) -> str: 4204 to = expression.this 4205 value = expression.expression 4206 style = expression.args.get("style") 4207 safe = expression.args.get("safe") 4208 strict = expression.args.get("strict") 4209 4210 if not to or not value: 4211 return "" 4212 4213 # Retrieve length of datatype and override to default if not specified 4214 if not seq_get(to.expressions, 0) and to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4215 to = exp.DataType.build(to.this, expressions=[exp.Literal.number(30)], nested=False) 4216 4217 transformed: t.Optional[exp.Expression] = None 4218 cast = exp.Cast if strict else exp.TryCast 4219 4220 # Check whether a conversion with format (T-SQL calls this 'style') is applicable 4221 if isinstance(style, exp.Literal) and style.is_int: 4222 from sqlglot.dialects.tsql import TSQL 4223 4224 style_value = style.name 4225 converted_style = TSQL.CONVERT_FORMAT_MAPPING.get(style_value) 4226 if not converted_style: 4227 self.unsupported(f"Unsupported T-SQL 'style' value: {style_value}") 4228 4229 fmt = exp.Literal.string(converted_style) 4230 4231 if to.this == exp.DataType.Type.DATE: 4232 transformed = exp.StrToDate(this=value, format=fmt) 4233 elif to.this in (exp.DataType.Type.DATETIME, exp.DataType.Type.DATETIME2): 4234 transformed = exp.StrToTime(this=value, format=fmt) 4235 elif to.this in self.PARAMETERIZABLE_TEXT_TYPES: 4236 transformed = cast(this=exp.TimeToStr(this=value, format=fmt), to=to, safe=safe) 4237 elif to.this == exp.DataType.Type.TEXT: 4238 transformed = exp.TimeToStr(this=value, format=fmt) 4239 4240 if not transformed: 4241 transformed = cast(this=value, to=to, safe=safe) 4242 4243 return self.sql(transformed)
4311 def copyparameter_sql(self, expression: exp.CopyParameter) -> str: 4312 option = self.sql(expression, "this") 4313 4314 if expression.expressions: 4315 upper = option.upper() 4316 4317 # Snowflake FILE_FORMAT options are separated by whitespace 4318 sep = " " if upper == "FILE_FORMAT" else ", " 4319 4320 # Databricks copy/format options do not set their list of values with EQ 4321 op = " " if upper in ("COPY_OPTIONS", "FORMAT_OPTIONS") else " = " 4322 values = self.expressions(expression, flat=True, sep=sep) 4323 return f"{option}{op}({values})" 4324 4325 value = self.sql(expression, "expression") 4326 4327 if not value: 4328 return option 4329 4330 op = " = " if self.COPY_PARAMS_EQ_REQUIRED else " " 4331 4332 return f"{option}{op}{value}"
4334 def credentials_sql(self, expression: exp.Credentials) -> str: 4335 cred_expr = expression.args.get("credentials") 4336 if isinstance(cred_expr, exp.Literal): 4337 # Redshift case: CREDENTIALS <string> 4338 credentials = self.sql(expression, "credentials") 4339 credentials = f"CREDENTIALS {credentials}" if credentials else "" 4340 else: 4341 # Snowflake case: CREDENTIALS = (...) 4342 credentials = self.expressions(expression, key="credentials", flat=True, sep=" ") 4343 credentials = f"CREDENTIALS = ({credentials})" if cred_expr is not None else "" 4344 4345 storage = self.sql(expression, "storage") 4346 storage = f"STORAGE_INTEGRATION = {storage}" if storage else "" 4347 4348 encryption = self.expressions(expression, key="encryption", flat=True, sep=" ") 4349 encryption = f" ENCRYPTION = ({encryption})" if encryption else "" 4350 4351 iam_role = self.sql(expression, "iam_role") 4352 iam_role = f"IAM_ROLE {iam_role}" if iam_role else "" 4353 4354 region = self.sql(expression, "region") 4355 region = f" REGION {region}" if region else "" 4356 4357 return f"{credentials}{storage}{encryption}{iam_role}{region}"
4359 def copy_sql(self, expression: exp.Copy) -> str: 4360 this = self.sql(expression, "this") 4361 this = f" INTO {this}" if self.COPY_HAS_INTO_KEYWORD else f" {this}" 4362 4363 credentials = self.sql(expression, "credentials") 4364 credentials = self.seg(credentials) if credentials else "" 4365 kind = self.seg("FROM" if expression.args.get("kind") else "TO") 4366 files = self.expressions(expression, key="files", flat=True) 4367 4368 sep = ", " if self.dialect.COPY_PARAMS_ARE_CSV else " " 4369 params = self.expressions( 4370 expression, 4371 key="params", 4372 sep=sep, 4373 new_line=True, 4374 skip_last=True, 4375 skip_first=True, 4376 indent=self.COPY_PARAMS_ARE_WRAPPED, 4377 ) 4378 4379 if params: 4380 if self.COPY_PARAMS_ARE_WRAPPED: 4381 params = f" WITH ({params})" 4382 elif not self.pretty: 4383 params = f" {params}" 4384 4385 return f"COPY{this}{kind} {files}{credentials}{params}"
4390 def datadeletionproperty_sql(self, expression: exp.DataDeletionProperty) -> str: 4391 on_sql = "ON" if expression.args.get("on") else "OFF" 4392 filter_col: t.Optional[str] = self.sql(expression, "filter_column") 4393 filter_col = f"FILTER_COLUMN={filter_col}" if filter_col else None 4394 retention_period: t.Optional[str] = self.sql(expression, "retention_period") 4395 retention_period = f"RETENTION_PERIOD={retention_period}" if retention_period else None 4396 4397 if filter_col or retention_period: 4398 on_sql = self.func("ON", filter_col, retention_period) 4399 4400 return f"DATA_DELETION={on_sql}"
def
maskingpolicycolumnconstraint_sql( self, expression: sqlglot.expressions.MaskingPolicyColumnConstraint) -> str:
4402 def maskingpolicycolumnconstraint_sql( 4403 self, expression: exp.MaskingPolicyColumnConstraint 4404 ) -> str: 4405 this = self.sql(expression, "this") 4406 expressions = self.expressions(expression, flat=True) 4407 expressions = f" USING ({expressions})" if expressions else "" 4408 return f"MASKING POLICY {this}{expressions}"
4418 def scoperesolution_sql(self, expression: exp.ScopeResolution) -> str: 4419 this = self.sql(expression, "this") 4420 expr = expression.expression 4421 4422 if isinstance(expr, exp.Func): 4423 # T-SQL's CLR functions are case sensitive 4424 expr = f"{self.sql(expr, 'this')}({self.format_args(*expr.expressions)})" 4425 else: 4426 expr = self.sql(expression, "expression") 4427 4428 return self.scope_resolution(expr, this)
4436 def rand_sql(self, expression: exp.Rand) -> str: 4437 lower = self.sql(expression, "lower") 4438 upper = self.sql(expression, "upper") 4439 4440 if lower and upper: 4441 return f"({upper} - {lower}) * {self.func('RAND', expression.this)} + {lower}" 4442 return self.func("RAND", expression.this)
4444 def changes_sql(self, expression: exp.Changes) -> str: 4445 information = self.sql(expression, "information") 4446 information = f"INFORMATION => {information}" 4447 at_before = self.sql(expression, "at_before") 4448 at_before = f"{self.seg('')}{at_before}" if at_before else "" 4449 end = self.sql(expression, "end") 4450 end = f"{self.seg('')}{end}" if end else "" 4451 4452 return f"CHANGES ({information}){at_before}{end}"
4454 def pad_sql(self, expression: exp.Pad) -> str: 4455 prefix = "L" if expression.args.get("is_left") else "R" 4456 4457 fill_pattern = self.sql(expression, "fill_pattern") or None 4458 if not fill_pattern and self.PAD_FILL_PATTERN_IS_REQUIRED: 4459 fill_pattern = "' '" 4460 4461 return self.func(f"{prefix}PAD", expression.this, expression.expression, fill_pattern)
def
explodinggenerateseries_sql(self, expression: sqlglot.expressions.ExplodingGenerateSeries) -> str:
4467 def explodinggenerateseries_sql(self, expression: exp.ExplodingGenerateSeries) -> str: 4468 generate_series = exp.GenerateSeries(**expression.args) 4469 4470 parent = expression.parent 4471 if isinstance(parent, (exp.Alias, exp.TableAlias)): 4472 parent = parent.parent 4473 4474 if self.SUPPORTS_EXPLODING_PROJECTIONS and not isinstance(parent, (exp.Table, exp.Unnest)): 4475 return self.sql(exp.Unnest(expressions=[generate_series])) 4476 4477 if isinstance(parent, exp.Select): 4478 self.unsupported("GenerateSeries projection unnesting is not supported.") 4479 4480 return self.sql(generate_series)
def
arrayconcat_sql( self, expression: sqlglot.expressions.ArrayConcat, name: str = 'ARRAY_CONCAT') -> str:
4482 def arrayconcat_sql(self, expression: exp.ArrayConcat, name: str = "ARRAY_CONCAT") -> str: 4483 exprs = expression.expressions 4484 if not self.ARRAY_CONCAT_IS_VAR_LEN: 4485 rhs = reduce(lambda x, y: exp.ArrayConcat(this=x, expressions=[y]), exprs) 4486 else: 4487 rhs = self.expressions(expression) 4488 4489 return self.func(name, expression.this, rhs or None)
4491 def converttimezone_sql(self, expression: exp.ConvertTimezone) -> str: 4492 if self.SUPPORTS_CONVERT_TIMEZONE: 4493 return self.function_fallback_sql(expression) 4494 4495 source_tz = expression.args.get("source_tz") 4496 target_tz = expression.args.get("target_tz") 4497 timestamp = expression.args.get("timestamp") 4498 4499 if source_tz and timestamp: 4500 timestamp = exp.AtTimeZone( 4501 this=exp.cast(timestamp, exp.DataType.Type.TIMESTAMPNTZ), zone=source_tz 4502 ) 4503 4504 expr = exp.AtTimeZone(this=timestamp, zone=target_tz) 4505 4506 return self.sql(expr)
4508 def json_sql(self, expression: exp.JSON) -> str: 4509 this = self.sql(expression, "this") 4510 this = f" {this}" if this else "" 4511 4512 _with = expression.args.get("with") 4513 4514 if _with is None: 4515 with_sql = "" 4516 elif not _with: 4517 with_sql = " WITHOUT" 4518 else: 4519 with_sql = " WITH" 4520 4521 unique_sql = " UNIQUE KEYS" if expression.args.get("unique") else "" 4522 4523 return f"JSON{this}{with_sql}{unique_sql}"
4525 def jsonvalue_sql(self, expression: exp.JSONValue) -> str: 4526 def _generate_on_options(arg: t.Any) -> str: 4527 return arg if isinstance(arg, str) else f"DEFAULT {self.sql(arg)}" 4528 4529 path = self.sql(expression, "path") 4530 returning = self.sql(expression, "returning") 4531 returning = f" RETURNING {returning}" if returning else "" 4532 4533 on_condition = self.sql(expression, "on_condition") 4534 on_condition = f" {on_condition}" if on_condition else "" 4535 4536 return self.func("JSON_VALUE", expression.this, f"{path}{returning}{on_condition}")
4538 def conditionalinsert_sql(self, expression: exp.ConditionalInsert) -> str: 4539 else_ = "ELSE " if expression.args.get("else_") else "" 4540 condition = self.sql(expression, "expression") 4541 condition = f"WHEN {condition} THEN " if condition else else_ 4542 insert = self.sql(expression, "this")[len("INSERT") :].strip() 4543 return f"{condition}{insert}"
4551 def oncondition_sql(self, expression: exp.OnCondition) -> str: 4552 # Static options like "NULL ON ERROR" are stored as strings, in contrast to "DEFAULT <expr> ON ERROR" 4553 empty = expression.args.get("empty") 4554 empty = ( 4555 f"DEFAULT {empty} ON EMPTY" 4556 if isinstance(empty, exp.Expression) 4557 else self.sql(expression, "empty") 4558 ) 4559 4560 error = expression.args.get("error") 4561 error = ( 4562 f"DEFAULT {error} ON ERROR" 4563 if isinstance(error, exp.Expression) 4564 else self.sql(expression, "error") 4565 ) 4566 4567 if error and empty: 4568 error = ( 4569 f"{empty} {error}" 4570 if self.dialect.ON_CONDITION_EMPTY_BEFORE_ERROR 4571 else f"{error} {empty}" 4572 ) 4573 empty = "" 4574 4575 null = self.sql(expression, "null") 4576 4577 return f"{empty}{error}{null}"
4583 def jsonexists_sql(self, expression: exp.JSONExists) -> str: 4584 this = self.sql(expression, "this") 4585 path = self.sql(expression, "path") 4586 4587 passing = self.expressions(expression, "passing") 4588 passing = f" PASSING {passing}" if passing else "" 4589 4590 on_condition = self.sql(expression, "on_condition") 4591 on_condition = f" {on_condition}" if on_condition else "" 4592 4593 path = f"{path}{passing}{on_condition}" 4594 4595 return self.func("JSON_EXISTS", this, path)
4597 def arrayagg_sql(self, expression: exp.ArrayAgg) -> str: 4598 array_agg = self.function_fallback_sql(expression) 4599 4600 # Add a NULL FILTER on the column to mimic the results going from a dialect that excludes nulls 4601 # on ARRAY_AGG (e.g Spark) to one that doesn't (e.g. DuckDB) 4602 if self.dialect.ARRAY_AGG_INCLUDES_NULLS and expression.args.get("nulls_excluded"): 4603 parent = expression.parent 4604 if isinstance(parent, exp.Filter): 4605 parent_cond = parent.expression.this 4606 parent_cond.replace(parent_cond.and_(expression.this.is_(exp.null()).not_())) 4607 else: 4608 this = expression.this 4609 # Do not add the filter if the input is not a column (e.g. literal, struct etc) 4610 if this.find(exp.Column): 4611 # DISTINCT is already present in the agg function, do not propagate it to FILTER as well 4612 this_sql = ( 4613 self.expressions(this) 4614 if isinstance(this, exp.Distinct) 4615 else self.sql(expression, "this") 4616 ) 4617 4618 array_agg = f"{array_agg} FILTER(WHERE {this_sql} IS NOT NULL)" 4619 4620 return array_agg
4628 def grant_sql(self, expression: exp.Grant) -> str: 4629 privileges_sql = self.expressions(expression, key="privileges", flat=True) 4630 4631 kind = self.sql(expression, "kind") 4632 kind = f" {kind}" if kind else "" 4633 4634 securable = self.sql(expression, "securable") 4635 securable = f" {securable}" if securable else "" 4636 4637 principals = self.expressions(expression, key="principals", flat=True) 4638 4639 grant_option = " WITH GRANT OPTION" if expression.args.get("grant_option") else "" 4640 4641 return f"GRANT {privileges_sql} ON{kind}{securable} TO {principals}{grant_option}"
4665 def overlay_sql(self, expression: exp.Overlay): 4666 this = self.sql(expression, "this") 4667 expr = self.sql(expression, "expression") 4668 from_sql = self.sql(expression, "from") 4669 for_sql = self.sql(expression, "for") 4670 for_sql = f" FOR {for_sql}" if for_sql else "" 4671 4672 return f"OVERLAY({this} PLACING {expr} FROM {from_sql}{for_sql})"
@unsupported_args('format')
def
todouble_sql(self, expression: sqlglot.expressions.ToDouble) -> str:
4678 def string_sql(self, expression: exp.String) -> str: 4679 this = expression.this 4680 zone = expression.args.get("zone") 4681 4682 if zone: 4683 # This is a BigQuery specific argument for STRING(<timestamp_expr>, <time_zone>) 4684 # BigQuery stores timestamps internally as UTC, so ConvertTimezone is used with UTC 4685 # set for source_tz to transpile the time conversion before the STRING cast 4686 this = exp.ConvertTimezone( 4687 source_tz=exp.Literal.string("UTC"), target_tz=zone, timestamp=this 4688 ) 4689 4690 return self.sql(exp.cast(this, exp.DataType.Type.VARCHAR))
def
overflowtruncatebehavior_sql(self, expression: sqlglot.expressions.OverflowTruncateBehavior) -> str:
4700 def overflowtruncatebehavior_sql(self, expression: exp.OverflowTruncateBehavior) -> str: 4701 filler = self.sql(expression, "this") 4702 filler = f" {filler}" if filler else "" 4703 with_count = "WITH COUNT" if expression.args.get("with_count") else "WITHOUT COUNT" 4704 return f"TRUNCATE{filler} {with_count}"
4706 def unixseconds_sql(self, expression: exp.UnixSeconds) -> str: 4707 if self.SUPPORTS_UNIX_SECONDS: 4708 return self.function_fallback_sql(expression) 4709 4710 start_ts = exp.cast( 4711 exp.Literal.string("1970-01-01 00:00:00+00"), to=exp.DataType.Type.TIMESTAMPTZ 4712 ) 4713 4714 return self.sql( 4715 exp.TimestampDiff(this=expression.this, expression=start_ts, unit=exp.var("SECONDS")) 4716 )
4718 def arraysize_sql(self, expression: exp.ArraySize) -> str: 4719 dim = expression.expression 4720 4721 # For dialects that don't support the dimension arg, we can safely transpile it's default value (1st dimension) 4722 if dim and self.ARRAY_SIZE_DIM_REQUIRED is None: 4723 if not (dim.is_int and dim.name == "1"): 4724 self.unsupported("Cannot transpile dimension argument for ARRAY_LENGTH") 4725 dim = None 4726 4727 # If dimension is required but not specified, default initialize it 4728 if self.ARRAY_SIZE_DIM_REQUIRED and not dim: 4729 dim = exp.Literal.number(1) 4730 4731 return self.func(self.ARRAY_SIZE_NAME, expression.this, dim)
4733 def attach_sql(self, expression: exp.Attach) -> str: 4734 this = self.sql(expression, "this") 4735 exists_sql = " IF NOT EXISTS" if expression.args.get("exists") else "" 4736 expressions = self.expressions(expression) 4737 expressions = f" ({expressions})" if expressions else "" 4738 4739 return f"ATTACH{exists_sql} {this}{expressions}"
4753 def featuresattime_sql(self, expression: exp.FeaturesAtTime) -> str: 4754 this_sql = self.sql(expression, "this") 4755 if isinstance(expression.this, exp.Table): 4756 this_sql = f"TABLE {this_sql}" 4757 4758 return self.func( 4759 "FEATURES_AT_TIME", 4760 this_sql, 4761 expression.args.get("time"), 4762 expression.args.get("num_rows"), 4763 expression.args.get("ignore_feature_nulls"), 4764 )
def
watermarkcolumnconstraint_sql(self, expression: sqlglot.expressions.WatermarkColumnConstraint) -> str:
4771 def encodeproperty_sql(self, expression: exp.EncodeProperty) -> str: 4772 encode = "KEY ENCODE" if expression.args.get("key") else "ENCODE" 4773 encode = f"{encode} {self.sql(expression, 'this')}" 4774 4775 properties = expression.args.get("properties") 4776 if properties: 4777 encode = f"{encode} {self.properties(properties)}" 4778 4779 return encode
4781 def includeproperty_sql(self, expression: exp.IncludeProperty) -> str: 4782 this = self.sql(expression, "this") 4783 include = f"INCLUDE {this}" 4784 4785 column_def = self.sql(expression, "column_def") 4786 if column_def: 4787 include = f"{include} {column_def}" 4788 4789 alias = self.sql(expression, "alias") 4790 if alias: 4791 include = f"{include} AS {alias}" 4792 4793 return include
def
partitionbyrangeproperty_sql(self, expression: sqlglot.expressions.PartitionByRangeProperty) -> str:
4799 def partitionbyrangeproperty_sql(self, expression: exp.PartitionByRangeProperty) -> str: 4800 partitions = self.expressions(expression, "partition_expressions") 4801 create = self.expressions(expression, "create_expressions") 4802 return f"PARTITION BY RANGE {self.wrap(partitions)} {self.wrap(create)}"
def
partitionbyrangepropertydynamic_sql( self, expression: sqlglot.expressions.PartitionByRangePropertyDynamic) -> str:
4804 def partitionbyrangepropertydynamic_sql( 4805 self, expression: exp.PartitionByRangePropertyDynamic 4806 ) -> str: 4807 start = self.sql(expression, "start") 4808 end = self.sql(expression, "end") 4809 4810 every = expression.args["every"] 4811 if isinstance(every, exp.Interval) and every.this.is_string: 4812 every.this.replace(exp.Literal.number(every.name)) 4813 4814 return f"START {self.wrap(start)} END {self.wrap(end)} EVERY {self.wrap(self.sql(every))}"
4827 def analyzestatistics_sql(self, expression: exp.AnalyzeStatistics) -> str: 4828 kind = self.sql(expression, "kind") 4829 option = self.sql(expression, "option") 4830 option = f" {option}" if option else "" 4831 this = self.sql(expression, "this") 4832 this = f" {this}" if this else "" 4833 columns = self.expressions(expression) 4834 columns = f" {columns}" if columns else "" 4835 return f"{kind}{option} STATISTICS{this}{columns}"
4837 def analyzehistogram_sql(self, expression: exp.AnalyzeHistogram) -> str: 4838 this = self.sql(expression, "this") 4839 columns = self.expressions(expression) 4840 inner_expression = self.sql(expression, "expression") 4841 inner_expression = f" {inner_expression}" if inner_expression else "" 4842 update_options = self.sql(expression, "update_options") 4843 update_options = f" {update_options} UPDATE" if update_options else "" 4844 return f"{this} HISTOGRAM ON {columns}{inner_expression}{update_options}"
def
analyzelistchainedrows_sql(self, expression: sqlglot.expressions.AnalyzeListChainedRows) -> str:
4855 def analyzevalidate_sql(self, expression: exp.AnalyzeValidate) -> str: 4856 kind = self.sql(expression, "kind") 4857 this = self.sql(expression, "this") 4858 this = f" {this}" if this else "" 4859 inner_expression = self.sql(expression, "expression") 4860 return f"VALIDATE {kind}{this}{inner_expression}"
4862 def analyze_sql(self, expression: exp.Analyze) -> str: 4863 options = self.expressions(expression, key="options", sep=" ") 4864 options = f" {options}" if options else "" 4865 kind = self.sql(expression, "kind") 4866 kind = f" {kind}" if kind else "" 4867 this = self.sql(expression, "this") 4868 this = f" {this}" if this else "" 4869 mode = self.sql(expression, "mode") 4870 mode = f" {mode}" if mode else "" 4871 properties = self.sql(expression, "properties") 4872 properties = f" {properties}" if properties else "" 4873 partition = self.sql(expression, "partition") 4874 partition = f" {partition}" if partition else "" 4875 inner_expression = self.sql(expression, "expression") 4876 inner_expression = f" {inner_expression}" if inner_expression else "" 4877 return f"ANALYZE{options}{kind}{this}{partition}{mode}{inner_expression}{properties}"
4879 def xmltable_sql(self, expression: exp.XMLTable) -> str: 4880 this = self.sql(expression, "this") 4881 namespaces = self.expressions(expression, key="namespaces") 4882 namespaces = f"XMLNAMESPACES({namespaces}), " if namespaces else "" 4883 passing = self.expressions(expression, key="passing") 4884 passing = f"{self.sep()}PASSING{self.seg(passing)}" if passing else "" 4885 columns = self.expressions(expression, key="columns") 4886 columns = f"{self.sep()}COLUMNS{self.seg(columns)}" if columns else "" 4887 by_ref = f"{self.sep()}RETURNING SEQUENCE BY REF" if expression.args.get("by_ref") else "" 4888 return f"XMLTABLE({self.sep('')}{self.indent(namespaces + this + passing + by_ref + columns)}{self.seg(')', sep='')}"
4894 def export_sql(self, expression: exp.Export) -> str: 4895 this = self.sql(expression, "this") 4896 connection = self.sql(expression, "connection") 4897 connection = f"WITH CONNECTION {connection} " if connection else "" 4898 options = self.sql(expression, "options") 4899 return f"EXPORT DATA {connection}{options} AS {this}"
4904 def declareitem_sql(self, expression: exp.DeclareItem) -> str: 4905 variable = self.sql(expression, "this") 4906 default = self.sql(expression, "default") 4907 default = f" = {default}" if default else "" 4908 4909 kind = self.sql(expression, "kind") 4910 if isinstance(expression.args.get("kind"), exp.Schema): 4911 kind = f"TABLE {kind}" 4912 4913 return f"{variable} AS {kind}{default}"
4915 def recursivewithsearch_sql(self, expression: exp.RecursiveWithSearch) -> str: 4916 kind = self.sql(expression, "kind") 4917 this = self.sql(expression, "this") 4918 set = self.sql(expression, "expression") 4919 using = self.sql(expression, "using") 4920 using = f" USING {using}" if using else "" 4921 4922 kind_sql = kind if kind == "CYCLE" else f"SEARCH {kind} FIRST BY" 4923 4924 return f"{kind_sql} {this} SET {set}{using}"
def
combinedparameterizedagg_sql(self, expression: sqlglot.expressions.CombinedParameterizedAgg) -> str:
4943 def get_put_sql(self, expression: exp.Put | exp.Get) -> str: 4944 # Snowflake GET/PUT statements: 4945 # PUT <file> <internalStage> <properties> 4946 # GET <internalStage> <file> <properties> 4947 props = expression.args.get("properties") 4948 props_sql = self.properties(props, prefix=" ", sep=" ", wrapped=False) if props else "" 4949 this = self.sql(expression, "this") 4950 target = self.sql(expression, "target") 4951 4952 if isinstance(expression, exp.Put): 4953 return f"PUT {this} {target}{props_sql}" 4954 else: 4955 return f"GET {target} {this}{props_sql}"